diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..4145eb9f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +bin/* +build/* +deps/lib/* + +project/vs*/ipch +project/vs*/*.opensdf +project/vs*/*.sdf +project/vs*/*.suo +project/vs*/*.user diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..94a9ed024 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 000000000..5bf5438ac --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Taiga + +Taiga is an open source, lightweight, anime tracker for Windows. It can automatically detect the episodes you're watching on your computer and synchronize your progress with online services such as [Hummingbird](http://hummingbird.me) and [MyAnimeList](http://myanimelist.net). It helps you manage your list, discover new series, share watched episodes and download new ones. + +Visit our [home page](http://taiga.erengy.com) for more information. See the [guidelines](https://github.com/erengy/taiga/wiki/Guidelines) if you'd like to contribute. Here's [how to compile](https://github.com/erengy/taiga/wiki/How-to-Compile). \ No newline at end of file diff --git a/Taiga.vcxproj b/Taiga.vcxproj deleted file mode 100644 index f162c1893..000000000 --- a/Taiga.vcxproj +++ /dev/null @@ -1,282 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {50BAD968-CEBF-46CA-A18A-FE3E8D625F94} - Win32Proj - Taiga - - - - Application - true - Unicode - - - Application - false - true - Unicode - - - - - - - - - - - - - true - - - false - - - - - - Level3 - Disabled - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - - - Windows - true - comctl32.lib;Oleacc.lib;psapi.lib;shlwapi.lib;uxtheme.lib;Winhttp.lib;Winmm.lib;%(AdditionalDependencies) - comctl32.dll - type=%27win32%27 name=%27Microsoft.Windows.Common-Controls%27 version=%276.0.0.0%27 processorArchitecture=%27x86%27 publicKeyToken=%276595b64144ccf1df%27 language=%27*%27;%(AdditionalManifestDependencies) - - - - - Level3 - - - MinSpace - true - true - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - Size - true - MultiThreaded - - - Windows - true - true - true - comctl32.lib;Oleacc.lib;psapi.lib;shlwapi.lib;uxtheme.lib;Winhttp.lib;Winmm.lib;%(AdditionalDependencies) - comctl32.dll - type=%27win32%27 name=%27Microsoft.Windows.Common-Controls%27 version=%276.0.0.0%27 processorArchitecture=%27x86%27 publicKeyToken=%276595b64144ccf1df%27 language=%27*%27;%(AdditionalManifestDependencies) - AsInvoker - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Taiga.vcxproj.filters b/Taiga.vcxproj.filters deleted file mode 100644 index c273155fd..000000000 --- a/Taiga.vcxproj.filters +++ /dev/null @@ -1,614 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {af8c685f-fbba-40d4-9a23-692afd6f5e53} - - - {58525987-9580-4e39-a613-a1b000b2498a} - - - {524be248-fa5b-482b-8f03-3b2a88a1e4f4} - - - {69e5923a-3694-4a7d-834b-59c61bb1fa00} - - - {4e9ae2e0-3d79-4f76-a2cb-14d890049aca} - - - {965e89b5-8902-466c-a26f-ab39814e4991} - - - {632c9571-5b54-4fc7-b4cb-25cb4abc7b96} - - - {1c1c56f5-5f1c-4db5-88f0-ed1f7286f97d} - - - {c8d3c2ca-aff5-47eb-a494-791f8c819306} - - - {0728981d-66d0-4e99-8cf4-2cd1aae17e53} - - - - - Dialogs - - - Dialogs - - - Dialogs - - - Dialogs - - - Dialogs - - - Dialogs - - - Dialogs - - - Dialogs - - - Dialogs - - - Dialogs - - - Dialogs - - - Dialogs - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Third Party\pugixml - - - Win32 - - - Win32 - - - Win32 - - - Win32 - - - Win32 - - - Win32 - - - Win32 - - - Win32\Controls - - - Win32\Controls - - - Win32\Controls - - - Win32\Controls - - - Win32\Controls - - - Win32\Controls - - - Win32\Controls - - - Win32\Controls - - - Win32\Controls - - - Win32\Controls - - - Win32\Controls - - - Win32\Controls - - - Third Party\oauth - - - Third Party\base64 - - - Source Files - - - Win32 - - - Source Files - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Win32\Controls - - - Win32 - - - Win32 - - - Win32 - - - Dialogs - - - Source Files - - - Source Files - - - Source Files - - - Dialogs - - - Dialogs - - - Source Files - - - Dialogs - - - Source Files - - - Anime - - - Anime - - - Anime - - - Source Files - - - Anime - - - Anime - - - Anime - - - Source Files - - - Dialogs - - - Source Files - - - Dialogs - - - Dialogs - - - Win32 - - - Win32\Controls - - - Source Files - - - Dialogs - - - - - Dialogs\Headers - - - Dialogs\Headers - - - Dialogs\Headers - - - Dialogs\Headers - - - Dialogs\Headers - - - Dialogs\Headers - - - Dialogs\Headers - - - Dialogs\Headers - - - Dialogs\Headers - - - Dialogs\Headers - - - Dialogs\Headers - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Third Party\pugixml - - - Third Party\pugixml - - - Win32 - - - Win32 - - - Win32 - - - Win32 - - - Win32 - - - Win32 - - - Win32 - - - Win32\Controls - - - Third Party\oauth - - - Third Party\base64 - - - Header Files - - - Win32 - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Third Party\zlib - - - Win32 - - - Win32 - - - Win32 - - - Dialogs\Headers - - - Header Files - - - Header Files - - - Dialogs\Headers - - - Dialogs\Headers - - - Header Files - - - Dialogs\Headers - - - Header Files - - - Header Files - - - Header Files - - - Anime - - - Anime - - - Anime - - - Header Files - - - Anime - - - Anime - - - Anime - - - Header Files - - - Header Files - - - Dialogs\Headers - - - Header Files - - - Dialogs\Headers - - - Dialogs\Headers - - - Win32 - - - Header Files - - - Header Files - - - Dialogs\Headers - - - - - Resource Files - - - Resource Files - - - \ No newline at end of file diff --git a/accessibility.h b/accessibility.h deleted file mode 100644 index 17bf64747..000000000 --- a/accessibility.h +++ /dev/null @@ -1,66 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef ACCESSIBILITY_H -#define ACCESSIBILITY_H - -#include "std.h" -#include - -// ============================================================================= - -class AccessibleChild { -public: - wstring name, role, value; - -public: - vector children; -}; - -class AccessibleObject { -public: - AccessibleObject(); - virtual ~AccessibleObject(); - - HRESULT FromWindow(HWND hwnd, DWORD object_id = OBJID_CLIENT); - - HRESULT BuildChildren(vector& children, IAccessible* acc = nullptr, LPARAM param = 0L); - HRESULT GetChildCount(long* child_count, IAccessible* acc = nullptr); - virtual bool AllowChildTraverse(AccessibleChild& child, LPARAM param = 0L); - - HRESULT GetName(wstring& name, long child_id = CHILDID_SELF, IAccessible* acc = nullptr); - HRESULT GetRole(wstring& role, long child_id = CHILDID_SELF, IAccessible* acc = nullptr); - HRESULT GetValue(wstring& value, long child_id = CHILDID_SELF, IAccessible* acc = nullptr); - - HWINEVENTHOOK Hook(DWORD eventMin, DWORD eventMax, - HMODULE hmodWinEventProc, WINEVENTPROC pfnWinEventProc, - DWORD idProcess, DWORD idThread, DWORD dwFlags); - bool IsHooked(); - void Unhook(); - - void Release(); - -public: - vector children; - -private: - IAccessible* acc_; - HWINEVENTHOOK win_event_hook_; -}; - -#endif // ACCESSIBILITY_H \ No newline at end of file diff --git a/action.cpp b/action.cpp deleted file mode 100644 index ae24fb58f..000000000 --- a/action.cpp +++ /dev/null @@ -1,754 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "anime.h" -#include "anime_db.h" -#include "anime_filter.h" -#include "announce.h" -#include "common.h" -#include "feed.h" -#include "foreach.h" -#include "history.h" -#include "http.h" -#include "logger.h" -#include "monitor.h" -#include "myanimelist.h" -#include "process.h" -#include "recognition.h" -#include "resource.h" -#include "settings.h" -#include "stats.h" -#include "string.h" -#include "taiga.h" -#include "theme.h" - -#include "dlg/dlg_about.h" -#include "dlg/dlg_anime_info.h" -#include "dlg/dlg_anime_info_page.h" -#include "dlg/dlg_anime_list.h" -#include "dlg/dlg_history.h" -#include "dlg/dlg_input.h" -#include "dlg/dlg_main.h" -#include "dlg/dlg_search.h" -#include "dlg/dlg_season.h" -#include "dlg/dlg_settings.h" -#include "dlg/dlg_test_recognition.h" -#include "dlg/dlg_torrent.h" -#include "dlg/dlg_feed_filter.h" -#include "dlg/dlg_update.h" - -#include "win32/win_taskbar.h" -#include "win32/win_taskdialog.h" - -// ============================================================================= - -void ExecuteAction(wstring action, WPARAM wParam, LPARAM lParam) { - LOG(LevelDebug, action); - - wstring body; - size_t pos = action.find('('); - if (pos != action.npos) { - body = action.substr(pos + 1, action.find_last_of(')') - (pos + 1)); - action.resize(pos); - } - Trim(body); - Trim(action); - if (action.empty()) return; - - // =========================================================================== - - // Synchronize() - // Synchronizes local and remote lists. - if (action == L"Synchronize") { -#ifdef _DEBUG - // Retrieve list - MainDialog.ChangeStatus(L"Downloading anime list..."); - bool result = mal::GetList(); - MainDialog.EnableInput(!result); - if (!result) MainDialog.ChangeStatus(); -#else - if (!Taiga.logged_in) { - // Log in - MainDialog.ChangeStatus(L"Logging in..."); - bool result = mal::Login(); - MainDialog.EnableInput(!result); - if (!result) MainDialog.ChangeStatus(); - } else { - if (History.queue.GetItemCount() > 0) { - // Update items in queue - History.queue.Check(false); - } else { - // Retrieve list - MainDialog.ChangeStatus(L"Synchronizing anime list..."); - bool result = mal::GetList(); - MainDialog.EnableInput(!result); - if (!result) MainDialog.ChangeStatus(); - } - } -#endif - - // =========================================================================== - - // Execute(path) - // Executes a file or folder. - } else if (action == L"Execute") { - Execute(body); - - // URL(address) - // Opens a web page. - // lParam is an anime ID. - } else if (action == L"URL") { - int anime_id = static_cast(lParam); - auto anime_item = AnimeDatabase.FindItem(anime_id); - if (anime_item) { - wstring title = anime_item->GetTitle(); - EraseChars(title, L"_!?.,:;~+"); - Erase(title, L" -"); - Replace(body, L"%title%", title); - } - ExecuteLink(body); - - // =========================================================================== - - // About() - // Shows about window. - } else if (action == L"About") { - if (!AboutDialog.IsWindow()) { - AboutDialog.Create(IDD_ABOUT, g_hMain, true); - } else { - ActivateWindow(AboutDialog.GetWindowHandle()); - } - - // CheckUpdates() - // Checks for a new version of the program. - } else if (action == L"CheckUpdates") { - if (!UpdateDialog.IsWindow()) { - UpdateDialog.Create(IDD_UPDATE, g_hMain, true); - } else { - ActivateWindow(UpdateDialog.GetWindowHandle()); - } - - // Exit(), Quit() - // Exits from Taiga. - } else if (action == L"Exit" || action == L"Quit") { - MainDialog.Destroy(); - - // Info() - // Shows anime information window. - // lParam is an anime ID. - } else if (action == L"Info") { - int anime_id = static_cast(lParam); - AnimeDialog.SetCurrentId(anime_id); - AnimeDialog.SetCurrentPage(INFOPAGE_SERIESINFO); - if (!AnimeDialog.IsWindow()) { - AnimeDialog.Create(IDD_ANIME_INFO, g_hMain, false); - } else { - ActivateWindow(AnimeDialog.GetWindowHandle()); - } - - // MainDialog() - } else if (action == L"MainDialog") { - if (!MainDialog.IsWindow()) { - MainDialog.Create(IDD_MAIN, NULL, false); - } else { - ActivateWindow(MainDialog.GetWindowHandle()); - } - - // RecognitionTest() - // Shows recognition test window. - } else if (action == L"RecognitionTest") { - if (!RecognitionTest.IsWindow()) { - RecognitionTest.Create(IDD_TEST_RECOGNITION, NULL, false); - } else { - ActivateWindow(RecognitionTest.GetWindowHandle()); - } - - // Settings() - // Shows settings window. - // wParam is the initial section. - // lParam is the initial page. - } else if (action == L"Settings") { - if (wParam > 0) - SettingsDialog.SetCurrentSection(wParam); - if (lParam > 0) - SettingsDialog.SetCurrentPage(lParam); - if (!SettingsDialog.IsWindow()) { - SettingsDialog.Create(IDD_SETTINGS, g_hMain, true); - } else { - ActivateWindow(SettingsDialog.GetWindowHandle()); - } - - // SearchAnime() - } else if (action == L"SearchAnime") { - if (body.empty()) return; - if (Settings.Account.MAL.user.empty() || Settings.Account.MAL.password.empty()) { - win32::TaskDialog dlg(APP_TITLE, TD_ICON_INFORMATION); - dlg.SetMainInstruction(L"Would you like to set your account information first?"); - dlg.SetContent(L"Anime search requires authentication, which means, " - L"you need to enter a valid username and password to search MyAnimeList."); - dlg.AddButton(L"Yes", IDYES); - dlg.AddButton(L"No", IDNO); - dlg.Show(g_hMain); - if (dlg.GetSelectedButtonID() == IDYES) - ExecuteAction(L"Settings", SECTION_SERVICES, PAGE_SERVICES_MAL); - return; - } - MainDialog.navigation.SetCurrentPage(SIDEBAR_ITEM_SEARCH); - MainDialog.edit.SetText(body); - SearchDialog.Search(body); - - // SearchTorrents(source) - // Searches torrents from specified source URL. - // lParam is an anime ID. - } else if (action == L"SearchTorrents") { - int anime_id = static_cast(lParam); - TorrentDialog.Search(body, anime_id); - - // ShowSidebar() - } else if (action == L"ShowSidebar") { - Settings.Program.General.hide_sidebar = !Settings.Program.General.hide_sidebar; - MainDialog.treeview.Show(!Settings.Program.General.hide_sidebar); - MainDialog.UpdateControlPositions(); - UpdateViewMenu(); - - // TorrentAddFilter() - // Shows add new filter window. - // wParam is a BOOL value that represents modal status. - // lParam is the handle of the parent window. - } else if (action == L"TorrentAddFilter") { - if (!FeedFilterDialog.IsWindow()) { - FeedFilterDialog.Create(IDD_FEED_FILTER, - reinterpret_cast(lParam), wParam != FALSE); - } else { - ActivateWindow(FeedFilterDialog.GetWindowHandle()); - } - - // ViewContent(page) - // Selects a page from sidebar. - } else if (action == L"ViewContent") { - int page = ToInt(body); - MainDialog.navigation.SetCurrentPage(page); - - // =========================================================================== - - // AddToListAs(status) - // Adds new anime to list with given status. - // lParam is an anime ID. - } else if (action == L"AddToListAs") { - int status = ToInt(body); - int anime_id = static_cast(lParam); - auto anime_item = AnimeDatabase.FindItem(anime_id); - // Add item to list - anime_item->AddtoUserList(); - AnimeDatabase.user.IncreaseItemCount(status, true); - AnimeDatabase.SaveList(anime_id, L"", L"", anime::ADD_ANIME); - // Add item to event queue - EventItem event_item; - event_item.anime_id = anime_id; - event_item.status = status; - if (status == mal::MYSTATUS_COMPLETED) { - event_item.episode = anime_item->GetEpisodeCount(); - event_item.date_finish = mal::TranslateDateForApi(GetDate()); - } - event_item.mode = HTTP_MAL_AnimeAdd; - History.queue.Add(event_item); - // Refresh - AnimeListDialog.RefreshList(status); - AnimeListDialog.RefreshTabs(status); - SearchDialog.RefreshList(); - if (AnimeDialog.GetCurrentId() == anime_id) - AnimeDialog.Refresh(); - if (NowPlayingDialog.GetCurrentId() == anime_id) - NowPlayingDialog.Refresh(); - - // ViewAnimePage - // Opens up anime page on MAL. - // lParam is an anime ID. - } else if (action == L"ViewAnimePage") { - int anime_id = static_cast(lParam); - mal::ViewAnimePage(anime_id); - - // ViewPanel(), ViewProfile(), ViewHistory() - // Opens up MyAnimeList user pages. - } else if (action == L"ViewPanel") { - mal::ViewPanel(); - } else if (action == L"ViewProfile") { - mal::ViewProfile(); - } else if (action == L"ViewHistory") { - mal::ViewHistory(); - - // ViewUpcomingAnime - // Opens up upcoming anime page on MAL. - } else if (action == L"ViewUpcomingAnime") { - mal::ViewUpcomingAnime(); - - // =========================================================================== - - // AddFolder() - // Opens up a dialog to add new root folder. - } else if (action == L"AddFolder") { - wstring path; - if (BrowseForFolder(g_hMain, L"Please select a folder:", L"", path)) { - Settings.Folders.root.push_back(path); - if (Settings.Folders.watch_enabled) FolderMonitor.Enable(); - ExecuteAction(L"Settings", SECTION_LIBRARY, PAGE_LIBRARY_FOLDERS); - } - - // ScanEpisodes(), ScanEpisodesAll() - // Checks episode availability. - } else if (action == L"ScanEpisodes") { - int anime_id = static_cast(lParam); - ScanAvailableEpisodes(anime_id, true, false); - } else if (action == L"ScanEpisodesAll") { - ScanAvailableEpisodes(anime::ID_UNKNOWN, true, false); - - // ToggleRecognition() - // Enables or disables anime recognition. - } else if (action == L"ToggleRecognition") { - Settings.Program.General.enable_recognition = !Settings.Program.General.enable_recognition; - if (Settings.Program.General.enable_recognition) { - MainDialog.ChangeStatus(L"Automatic anime recognition is now enabled."); - CurrentEpisode.Set(anime::ID_UNKNOWN); - } else { - MainDialog.ChangeStatus(L"Automatic anime recognition is now disabled."); - auto anime_item = AnimeDatabase.FindItem(CurrentEpisode.anime_id); - CurrentEpisode.Set(anime::ID_NOTINLIST); - if (anime_item) anime_item->EndWatching(CurrentEpisode); - } - - // ToggleSharing() - // Enables or disables automatic sharing. - } else if (action == L"ToggleSharing") { - Settings.Program.General.enable_sharing = !Settings.Program.General.enable_sharing; - UpdateToolsMenu(); - if (Settings.Program.General.enable_sharing) { - MainDialog.ChangeStatus(L"Automatic sharing is now enabled."); - } else { - MainDialog.ChangeStatus(L"Automatic sharing is now disabled."); - } - - // ToggleSynchronization() - // Enables or disables automatic list synchronization. - } else if (action == L"ToggleSynchronization") { - Settings.Program.General.enable_sync = !Settings.Program.General.enable_sync; - UpdateToolsMenu(); - if (Settings.Program.General.enable_sync) { - MainDialog.ChangeStatus(L"Automatic synchronization is now enabled."); - } else { - MainDialog.ChangeStatus(L"Automatic synchronization is now disabled."); - } - - // =========================================================================== - - // AnnounceToHTTP(force) - // Sends an HTTP request. - } else if (action == L"AnnounceToHTTP") { - Announcer.Do(ANNOUNCE_TO_HTTP, nullptr, body == L"true"); - - // AnnounceToMessenger(force) - // Changes MSN Messenger status text. - } else if (action == L"AnnounceToMessenger") { - Announcer.Do(ANNOUNCE_TO_MESSENGER, nullptr, body == L"true"); - - // AnnounceToMIRC(force) - // Sends message to specified channels in mIRC. - } else if (action == L"AnnounceToMIRC") { - Announcer.Do(ANNOUNCE_TO_MIRC, nullptr, body == L"true"); - - // AnnounceToSkype(force) - // Changes Skype mood text. - // Requires authorization. - } else if (action == L"AnnounceToSkype") { - Announcer.Do(ANNOUNCE_TO_SKYPE, nullptr, body == L"true"); - - // AnnounceToTwitter(force) - // Changes Twitter status. - } else if (action == L"AnnounceToTwitter") { - Announcer.Do(ANNOUNCE_TO_TWITTER, nullptr, body == L"true"); - - // =========================================================================== - - // EditAll([anime_id]) - // Shows a dialog to edit details of an anime. - // lParam is an anime ID. - } else if (action == L"EditAll") { - int anime_id = body.empty() ? static_cast(lParam) : ToInt(body); - auto anime_item = AnimeDatabase.FindItem(anime_id); - if (!anime_item || !anime_item->IsInList()) return; - AnimeDialog.SetCurrentId(anime_id); - AnimeDialog.SetCurrentPage(INFOPAGE_MYINFO); - if (!AnimeDialog.IsWindow()) { - AnimeDialog.Create(IDD_ANIME_INFO, g_hMain, false); - } else { - ActivateWindow(AnimeDialog.GetWindowHandle()); - } - - // EditDelete() - // Removes an anime from list. - // lParam is an anime ID. - } else if (action == L"EditDelete") { - int anime_id = static_cast(lParam); - auto anime_item = AnimeDatabase.FindItem(anime_id); - win32::TaskDialog dlg; - dlg.SetWindowTitle(anime_item->GetTitle().c_str()); - dlg.SetMainIcon(TD_ICON_INFORMATION); - dlg.SetMainInstruction(L"Are you sure you want to delete this title from your list?"); - dlg.AddButton(L"Yes", IDYES); - dlg.AddButton(L"No", IDNO); - dlg.Show(g_hMain); - if (dlg.GetSelectedButtonID() == IDYES) { - EventItem item; - item.anime_id = anime_id; - item.mode = HTTP_MAL_AnimeDelete; - History.queue.Add(item); - } - - // EditEpisode(value) - // Changes watched episode value of an anime. - // Value is optional. - // lParam is an anime ID. - } else if (action == L"EditEpisode") { - int anime_id = static_cast(lParam); - auto anime_item = AnimeDatabase.FindItem(anime_id); - int value = -1; - if (body.empty()) { - InputDialog dlg; - dlg.SetNumbers(true, 0, anime_item->GetEpisodeCount(), anime_item->GetMyLastWatchedEpisode()); - dlg.title = anime_item->GetTitle(); - dlg.info = L"Please enter episode number for this title:"; - dlg.text = ToWstr(anime_item->GetMyLastWatchedEpisode()); - dlg.Show(g_hMain); - if (dlg.result == IDOK) { - value = ToInt(dlg.text); - } - } else { - value = ToInt(body); - } - if (mal::IsValidEpisode(value, -1, anime_item->GetEpisodeCount())) { - anime::Episode episode; - episode.number = ToWstr(value); - anime_item->AddToQueue(episode, true); - } - // DecrementEpisode() - // lParam is an anime ID. - } else if (action == L"DecrementEpisode") { - int anime_id = static_cast(lParam); - auto anime_item = AnimeDatabase.FindItem(anime_id); - int watched = anime_item->GetMyLastWatchedEpisode(); - auto event_item = History.queue.FindItem(anime_item->GetId(), EVENT_SEARCH_EPISODE); - if (event_item && *event_item->episode == watched && - watched > anime_item->GetMyLastWatchedEpisode(false)) { - event_item->enabled = false; - History.queue.RemoveDisabled(); - } else { - if (mal::IsValidEpisode(watched - 1, -1, anime_item->GetEpisodeCount())) { - anime::Episode episode; - episode.number = ToWstr(watched - 1); - anime_item->AddToQueue(episode, true); - } - } - // IncrementEpisode() - // lParam is an anime ID. - } else if (action == L"IncrementEpisode") { - int anime_id = static_cast(lParam); - auto anime_item = AnimeDatabase.FindItem(anime_id); - int watched = anime_item->GetMyLastWatchedEpisode(); - if (mal::IsValidEpisode(watched + 1, watched, anime_item->GetEpisodeCount())) { - anime::Episode episode; - episode.number = ToWstr(watched + 1); - anime_item->AddToQueue(episode, true); - } - - // EditScore(value) - // Changes anime score. - // Value must be between 0-10 and different from current score. - // lParam is an anime ID. - } else if (action == L"EditScore") { - int anime_id = static_cast(lParam); - EventItem item; - item.anime_id = anime_id; - item.score = ToInt(body); - item.mode = HTTP_MAL_AnimeUpdate; - History.queue.Add(item); - - // EditStatus(value) - // Changes anime status of user. - // Value must be 1, 2, 3, 4 or 6, and different from current status. - // lParam is an anime ID. - } else if (action == L"EditStatus") { - EventItem event_item; - event_item.status = ToInt(body); - int anime_id = static_cast(lParam); - auto anime_item = AnimeDatabase.FindItem(anime_id); - switch (*event_item.status) { - case mal::MYSTATUS_COMPLETED: - event_item.episode = anime_item->GetEpisodeCount(); - if (*event_item.episode == 0) event_item.episode.Reset(); - if (!mal::IsValidDate(anime_item->GetMyDate(anime::DATE_START))) - if (anime_item->GetEpisodeCount() == 1) - event_item.date_start = mal::TranslateDateForApi(GetDate()); - if (!mal::IsValidDate(anime_item->GetMyDate(anime::DATE_END))) - event_item.date_finish = mal::TranslateDateForApi(GetDate()); - break; - } - event_item.anime_id = anime_id; - event_item.mode = HTTP_MAL_AnimeUpdate; - History.queue.Add(event_item); - - // EditTags(tags) - // Changes anime tags. - // Tags must be separated by a comma. - // lParam is an anime ID. - } else if (action == L"EditTags") { - int anime_id = static_cast(lParam); - auto anime_item = AnimeDatabase.FindItem(anime_id); - InputDialog dlg; - dlg.title = anime_item->GetTitle(); - dlg.info = L"Please enter tags for this title, separated by a comma:"; - dlg.text = anime_item->GetMyTags(); - dlg.Show(g_hMain); - if (dlg.result == IDOK) { - EventItem item; - item.anime_id = anime_id; - item.tags = dlg.text; - item.mode = HTTP_MAL_AnimeUpdate; - History.queue.Add(item); - } - - // EditTitles(titles) - // Changes alternative titles of an anime. - // Titles must be separated by "; ". - // lParam is an anime ID. - } else if (action == L"EditTitles") { - int anime_id = static_cast(lParam); - auto anime_item = AnimeDatabase.FindItem(anime_id); - InputDialog dlg; - dlg.title = anime_item->GetTitle(); - dlg.info = L"Please enter alternative titles, separated by a semicolon:"; - dlg.text = Join(anime_item->GetUserSynonyms(), L"; "); - dlg.Show(g_hMain); - if (dlg.result == IDOK) { - anime_item->SetUserSynonyms(dlg.text); - Meow.UpdateCleanTitles(anime_id); - Settings.Save(); - } - - // =========================================================================== - - // OpenFolder() - // Searches for anime folder and opens it. - // lParam is an anime ID. - } else if (action == L"OpenFolder") { - int anime_id = static_cast(lParam); - auto anime_item = AnimeDatabase.FindItem(anime_id); - if (!anime_item || !anime_item->IsInList()) return; - MainDialog.ChangeStatus(L"Searching for folder..."); - if (!anime_item->CheckFolder()) { - win32::TaskDialog dlg; - dlg.SetWindowTitle(L"Folder Not Found"); - dlg.SetMainIcon(TD_ICON_INFORMATION); - dlg.SetMainInstruction(L"Taiga couldn't find the folder of this anime. " - L"Would you like to set it manually?"); - dlg.AddButton(L"Yes", IDYES); - dlg.AddButton(L"No", IDNO); - dlg.Show(g_hMain); - if (dlg.GetSelectedButtonID() == IDYES) { - wstring default_path, path; - if (!Settings.Folders.root.empty()) - default_path = Settings.Folders.root.front(); - if (BrowseForFolder(g_hMain, L"Choose an anime folder", default_path, path)) { - anime_item->SetFolder(path); - Settings.Save(); - } - } - } - MainDialog.ChangeStatus(); - if (!anime_item->GetFolder().empty()) { - Execute(anime_item->GetFolder()); - } - - // SetFolder() - // Lets user set an anime folder. - // lParam is an anime ID. - } else if (action == L"SetFolder") { - int anime_id = static_cast(lParam); - auto anime_item = AnimeDatabase.FindItem(anime_id); - wstring path, title = L"Anime title: " + anime_item->GetTitle(); - if (BrowseForFolder(MainDialog.GetWindowHandle(), title.c_str(), L"", path)) { - anime_item->SetFolder(path); - Settings.Save(); - anime_item->CheckEpisodes(); - } - - // =========================================================================== - - // PlayEpisode(value) - // Searches for an episode of an anime and plays it. - // lParam is an anime ID. - } else if (action == L"PlayEpisode") { - int number = ToInt(body); - int anime_id = static_cast(lParam); - auto anime_item = AnimeDatabase.FindItem(anime_id); - anime_item->PlayEpisode(number); - - // PlayLast() - // Searches for the last watched episode of an anime and plays it. - // lParam is an anime ID. - } else if (action == L"PlayLast") { - int anime_id = static_cast(lParam); - auto anime_item = AnimeDatabase.FindItem(anime_id); - int number = anime_item->GetMyLastWatchedEpisode(); - anime_item->PlayEpisode(number); - - // PlayNext([anime_id]) - // Searches for the next episode of an anime and plays it. - // lParam is an anime ID. - } else if (action == L"PlayNext") { - int anime_id = body.empty() ? static_cast(lParam) : ToInt(body); - auto anime_item = AnimeDatabase.FindItem(anime_id); - if (anime_item->GetEpisodeCount() != 1) { - anime_item->PlayEpisode(anime_item->GetMyLastWatchedEpisode() + 1); - } else { - anime_item->PlayEpisode(1); - } - - // PlayRandom() - // Searches for a random episode of an anime and plays it. - // lParam is an anime ID. - } else if (action == L"PlayRandom") { - int anime_id = body.empty() ? static_cast(lParam) : ToInt(body); - auto anime_item = AnimeDatabase.FindItem(anime_id); - if (anime_item && anime_item->CheckFolder()) { - int total = anime_item->GetEpisodeCount(); - if (total == 0) - total = anime_item->GetMyLastWatchedEpisode() + 1; - wstring path; - srand(static_cast(GetTickCount())); - for (int i = 0; i < total; i++) { - int episode_number = rand() % total + 1; - path = SearchFileFolder(*anime_item, anime_item->GetFolder(), episode_number, false); - if (!path.empty()) { - Execute(path); - return; - } - } - } - win32::TaskDialog dlg; - dlg.SetWindowTitle(L"Play Random Episode"); - dlg.SetMainIcon(TD_ICON_ERROR); - dlg.SetMainInstruction(L"Could not find any episode to play."); - dlg.Show(g_hMain); - - // PlayRandomAnime() - // Searches for a random episode of a random anime and plays it. - } else if (action == L"PlayRandomAnime") { - static time_t time_last_checked = 0; - time_t time_now = time(nullptr); - if (time_now > time_last_checked + (60 * 2)) { // 2 minutes - ScanAvailableEpisodes(anime::ID_UNKNOWN, false, false); - time_last_checked = time_now; - } - std::vector valid_ids; - foreach_(it, AnimeDatabase.items) { - anime::Item& anime_item = it->second; - if (!anime_item.IsInList()) - continue; - if (!anime_item.IsNewEpisodeAvailable()) - continue; - switch (anime_item.GetMyStatus()) { - case mal::MYSTATUS_NOTINLIST: - case mal::MYSTATUS_COMPLETED: - case mal::MYSTATUS_DROPPED: - continue; - } - valid_ids.push_back(anime_item.GetId()); - } - foreach_ (id, valid_ids) { - srand(static_cast(GetTickCount())); - size_t max_value = valid_ids.size(); - size_t index = rand() % max_value + 1; - int anime_id = valid_ids.at(index); - auto anime_item = AnimeDatabase.FindItem(anime_id); - if (anime_item->PlayEpisode(anime_item->GetMyLastWatchedEpisode() + 1)) - return; - } - win32::TaskDialog dlg; - dlg.SetWindowTitle(L"Play Random Anime"); - dlg.SetMainIcon(TD_ICON_ERROR); - dlg.SetMainInstruction(L"Could not find any episode to play."); - dlg.Show(g_hMain); - - // =========================================================================== - - // Season_Load(file) - // Loads season data. - } else if (action == L"Season_Load") { - if (SeasonDatabase.Load(body)) { - SeasonDatabase.Review(); - SeasonDialog.RefreshList(); - SeasonDialog.RefreshStatus(); - SeasonDialog.RefreshToolbar(); - if (SeasonDatabase.IsRefreshRequired()) { - win32::TaskDialog dlg; - wstring title = L"Season - " + SeasonDatabase.name; - dlg.SetWindowTitle(title.c_str()); - dlg.SetMainIcon(TD_ICON_INFORMATION); - dlg.SetMainInstruction(L"Would you like to refresh this season's data?"); - dlg.SetContent(L"It seems that we don't know much about some anime titles in this season. " - L"Taiga will connect to MyAnimeList to retrieve missing information and images."); - dlg.AddButton(L"Yes", IDYES); - dlg.AddButton(L"No", IDNO); - dlg.Show(g_hMain); - if (dlg.GetSelectedButtonID() == IDYES) - SeasonDialog.RefreshData(); - } - } - - // Season_GroupBy(group) - // Groups season data. - } else if (action == L"Season_GroupBy") { - SeasonDialog.group_by = ToInt(body); - SeasonDialog.RefreshList(); - SeasonDialog.RefreshToolbar(); - - // Season_SortBy(sort) - // Sorts season data. - } else if (action == L"Season_SortBy") { - SeasonDialog.sort_by = ToInt(body); - SeasonDialog.RefreshList(); - SeasonDialog.RefreshToolbar(); - - // Season_RefreshItemData() - // Refreshes an individual season item data. - } else if (action == L"Season_RefreshItemData") { - SeasonDialog.RefreshData(static_cast(lParam)); - - // Season_ViewAs(mode) - // Changes view mode. - } else if (action == L"Season_ViewAs") { - SeasonDialog.SetViewMode(ToInt(body)); - SeasonDialog.RefreshList(); - SeasonDialog.RefreshToolbar(); - - // Unknown - } else { - LOG(LevelWarning, L"Unknown action: " + action); - } -} \ No newline at end of file diff --git a/anime.cpp b/anime.cpp deleted file mode 100644 index 9d53ca2fd..000000000 --- a/anime.cpp +++ /dev/null @@ -1,325 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "anime.h" -#include "anime_db.h" -#include "anime_episode.h" - -#include "announce.h" -#include "common.h" -#include "feed.h" -#include "foreach.h" -#include "history.h" -#include "media.h" -#include "myanimelist.h" -#include "process.h" -#include "settings.h" -#include "string.h" -#include "taiga.h" -#include "theme.h" - -#include "dlg/dlg_anime_info.h" -#include "dlg/dlg_anime_list.h" -#include "dlg/dlg_main.h" - -#include "win32/win_taskbar.h" -#include "win32/win_taskdialog.h" - -namespace anime { - -// ============================================================================= - -SeriesInformation::SeriesInformation() - : id(ID_UNKNOWN), - type(mal::TYPE_UNKNOWN), - episodes(-1), - status(mal::STATUS_UNKNOWN) { -} - -MyInformation::MyInformation() - : watched_episodes(0), - score(0), - status(mal::MYSTATUS_NOTINLIST), - rewatching(FALSE), - rewatching_ep(0) { -} - -LocalInformation::LocalInformation() - : last_aired_episode(0), - playing(false), - use_alternative(false) { -} - -// ============================================================================= - -void Item::StartWatching(Episode& episode) { - // Make sure item is in list - if (!IsInList()) AddtoUserList(); - - // Change status - Taiga.play_status = PLAYSTATUS_PLAYING; - SetPlaying(true); - - // Update now playing window - NowPlayingDialog.SetCurrentId(GetId()); - - // Update anime list window - int status = GetMyRewatching() ? mal::MYSTATUS_WATCHING : GetMyStatus(); - if (status != mal::MYSTATUS_NOTINLIST) { - AnimeListDialog.RefreshList(status); - AnimeListDialog.RefreshTabs(status); - } - int list_index = AnimeListDialog.GetListIndex(GetId()); - if (list_index > -1) { - AnimeListDialog.listview.SetItemIcon(list_index, ICON16_PLAY); - AnimeListDialog.listview.RedrawItems(list_index, list_index, true); - AnimeListDialog.listview.EnsureVisible(list_index); - } - - // Update main window - MainDialog.UpdateTip(); - MainDialog.UpdateTitle(); - if (Settings.Account.Update.go_to_nowplaying) - MainDialog.navigation.SetCurrentPage(SIDEBAR_ITEM_NOWPLAYING); - - // Show balloon tip - if (Settings.Program.Notifications.recognized) { - Taiga.current_tip_type = TIPTYPE_NOWPLAYING; - Taskbar.Tip(L"", L"", 0); - Taskbar.Tip(ReplaceVariables(Settings.Program.Notifications.format, episode).c_str(), - L"Now Playing", NIIF_INFO); - } - - // Check folder - if (GetFolder().empty()) { - if (episode.folder.empty()) { - HWND hwnd = MediaPlayers.items[MediaPlayers.index].window_handle; - episode.folder = MediaPlayers.GetTitleFromProcessHandle(hwnd); - episode.folder = GetPathOnly(episode.folder); - } - if (IsInsideRootFolders(episode.folder)) { - // Set the folder if only it is under a root folder - SetFolder(episode.folder); - Settings.Save(); - } - } - - // Get additional information - if (GetScore().empty() || GetSynopsis().empty()) - mal::SearchAnime(GetId(), GetTitle()); - - // Update list - if (Settings.Account.Update.delay == 0 && !Settings.Account.Update.wait_mp) - UpdateList(episode); -} - -void Item::EndWatching(Episode episode) { - // Change status - Taiga.play_status = PLAYSTATUS_STOPPED; - SetPlaying(false); - - // Announce - episode.anime_id = GetId(); - Announcer.Do(ANNOUNCE_TO_HTTP, &episode); - Announcer.Clear(ANNOUNCE_TO_MESSENGER | ANNOUNCE_TO_SKYPE); - - // Update now playing window - NowPlayingDialog.SetCurrentId(anime::ID_UNKNOWN); - - // Update main window - episode.anime_id = anime::ID_UNKNOWN; - MainDialog.UpdateTip(); - MainDialog.UpdateTitle(); - int list_index = AnimeListDialog.GetListIndex(GetId()); - if (list_index > -1) { - AnimeListDialog.listview.SetItemIcon(list_index, StatusToIcon(GetAiringStatus())); - AnimeListDialog.listview.RedrawItems(list_index, list_index, true); - } -} - -bool Item::IsUpdateAllowed(const Episode& episode, bool ignore_update_time) { - if (episode.processed) - return false; - - if (!ignore_update_time) - if (Settings.Account.Update.delay > Taiga.ticker_media) - if (Taiga.ticker_media > -1) - return false; - - if (GetMyStatus() == mal::MYSTATUS_COMPLETED && GetMyRewatching() == 0) - return false; - - int number = GetEpisodeHigh(episode.number); - int number_low = GetEpisodeLow(episode.number); - int last_watched = GetMyLastWatchedEpisode(); - - if (Settings.Account.Update.out_of_range) - if (number_low > last_watched + 1 || number < last_watched + 1) - return false; - - if (!mal::IsValidEpisode(number, last_watched, GetEpisodeCount())) - return false; - - return true; -} - -void Item::UpdateList(Episode& episode) { - if (!IsUpdateAllowed(episode, false)) - return; - - episode.processed = true; - - if (Settings.Account.Update.ask_to_confirm) { - ConfirmationQueue.Add(episode); - ConfirmationQueue.Process(); - } else { - AddToQueue(episode, true); - } -} - -void Item::AddToQueue(const Episode& episode, bool change_status) { - // Create event item - EventItem event_item; - event_item.anime_id = GetId(); - - // Set episode number - event_item.episode = GetEpisodeHigh(episode.number); - - // Set start/finish date - if (*event_item.episode == 1 && !mal::IsValidDate(GetMyDate(DATE_START))) - event_item.date_start = mal::TranslateDateForApi(::GetDate()); - if (*event_item.episode == GetEpisodeCount() && !mal::IsValidDate(GetMyDate(DATE_END))) - event_item.date_finish = mal::TranslateDateForApi(::GetDate()); - - // Set update mode - if (GetMyStatus() == mal::MYSTATUS_NOTINLIST) { - event_item.mode = HTTP_MAL_AnimeAdd; - change_status = true; - } else { - event_item.mode = HTTP_MAL_AnimeUpdate; - } - - if (change_status) { - // Move to completed - if (GetEpisodeCount() == *event_item.episode) { - event_item.status = mal::MYSTATUS_COMPLETED; - if (GetMyRewatching()) { - event_item.enable_rewatching = FALSE; - //event_item.times_rewatched++; // TODO: Enable when MAL adds to API - } - // Move to watching - } else if (GetMyStatus() != mal::MYSTATUS_WATCHING || *event_item.episode == 1) { - if (!GetMyRewatching()) { - event_item.status = mal::MYSTATUS_WATCHING; - } - } - } - - // Add event to queue - History.queue.Add(event_item); -} - -// ============================================================================= - -bool GetFansubFilter(int anime_id, vector& groups) { - bool found = false; - - foreach_(i, Aggregator.filter_manager.filters) { - if (found) break; - foreach_(j, i->anime_ids) { - if (*j != anime_id) continue; - if (found) break; - foreach_(k, i->conditions) { - if (k->element == FEED_FILTER_ELEMENT_EPISODE_GROUP) { - groups.push_back(k->value); - found = true; - } - } - } - } - - return found; -} - -bool SetFansubFilter(int anime_id, const wstring& group_name) { - // Check existing filters - foreach_(i, Aggregator.filter_manager.filters) { - foreach_(j, i->anime_ids) { - if (*j != anime_id) continue; - foreach_(k, i->conditions) { - if (k->element == FEED_FILTER_ELEMENT_EPISODE_GROUP) { - if (group_name.empty()) { - Aggregator.filter_manager.filters.erase(i); - } else { - k->value = group_name; - } - return true; - } - } - } - } - - if (group_name.empty()) - return false; - - // Create new filter - auto anime_item = AnimeDatabase.FindItem(anime_id); - Aggregator.filter_manager.AddFilter( - FEED_FILTER_ACTION_PREFER, FEED_FILTER_MATCH_ALL, FEED_FILTER_OPTION_DEFAULT, - true, L"[Fansub] " + anime_item->GetTitle()); - Aggregator.filter_manager.filters.back().AddCondition( - FEED_FILTER_ELEMENT_EPISODE_GROUP, FEED_FILTER_OPERATOR_EQUALS, - group_name); - Aggregator.filter_manager.filters.back().anime_ids.push_back(anime_id); - return true; -} - -wstring GetImagePath(int anime_id) { - wstring path = Taiga.GetDataPath() + L"db\\image\\"; - if (anime_id > 0) path += ToWstr(anime_id) + L".jpg"; - return path; -} - -void GetUpcomingTitles(vector& anime_ids) { - foreach_c_(item, AnimeDatabase.items) { - const anime::Item& anime_item = item->second; - - const Date& date_start = anime_item.GetDate(anime::DATE_START); - const Date& date_now = GetDateJapan(); - - if (!date_start.year || !date_start.month || !date_start.day) - continue; - - if (date_start > date_now && - ToDayCount(date_start) < ToDayCount(date_now) + 7) { // Same week - anime_ids.push_back(anime_item.GetId()); - } - } -} - -bool IsInsideRootFolders(const wstring& path) { - foreach_c_(root_folder, Settings.Folders.root) - if (StartsWith(path, *root_folder)) - return true; - - return false; -} - -} // namespace anime \ No newline at end of file diff --git a/anime.h b/anime.h deleted file mode 100644 index 1a649458e..000000000 --- a/anime.h +++ /dev/null @@ -1,125 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef ANIME_H -#define ANIME_H - -#include "std.h" - -#include "time.h" - -namespace anime { - -// ============================================================================= - -// ID_NOTINLIST -// Used in Episode data to denote the item is not in user's list. -// Item may or may not be in the database. -// -// ID_UNKNOWN -// There's no item in the database with this ID. -// This is the default ID for all anime items. -// -enum AnimeId { - ID_NOTINLIST = -1, - ID_UNKNOWN = 0 -}; - -// GetDate() and SetDate() require a particular type. -// -// DATE_START -// Airing date of the first episode (i.e., season premiere for TV series). -// -// DATE_END -// Airing date of the last episode (i.e., season finale for TV series). -// The same as DATE_START for one-episode titles. -// -enum DateType { - DATE_START = 0, - DATE_END -}; - -// All anime items have series information. -class SeriesInformation { - public: - SeriesInformation(); - virtual ~SeriesInformation() {} - - int id; - int type; - int episodes; - int status; - wstring title; - vector synonyms; - Date date_start; - Date date_end; - wstring image_url; - - wstring english_title; - wstring genres; - wstring popularity; - wstring producers; - wstring rank; - wstring score; - wstring synopsis; -}; - -// Invalid for anime items that are not in user's list. -class MyInformation { - public: - MyInformation(); - virtual ~MyInformation() {} - - int watched_episodes; - int score; - int status; - int rewatching; - int rewatching_ep; - Date date_start; - Date date_finish; - wstring last_updated; - wstring tags; -}; - -// For all kinds of other temporary information -class LocalInformation { - public: - LocalInformation(); - virtual ~LocalInformation() {} - - int last_aired_episode; - vector available_episodes; - wstring new_episode_path; - wstring folder; - vector synonyms; - bool playing; - bool use_alternative; -}; - -bool GetFansubFilter(int anime_id, vector& groups); -bool SetFansubFilter(int anime_id, const wstring& group_name); - -wstring GetImagePath(int anime_id = -1); - -void GetUpcomingTitles(vector& anime_ids); - -bool IsInsideRootFolders(const wstring& path); - -} // namespace anime - -#endif // ANIME_H \ No newline at end of file diff --git a/anime_db.cpp b/anime_db.cpp deleted file mode 100644 index 137f9efb1..000000000 --- a/anime_db.cpp +++ /dev/null @@ -1,629 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" -#include - -#include "anime.h" -#include "anime_db.h" - -#include "common.h" -#include "logger.h" -#include "myanimelist.h" -#include "recognition.h" -#include "settings.h" -#include "string.h" -#include "taiga.h" -#include "xml.h" - -#include "dlg/dlg_anime_info.h" -#include "dlg/dlg_anime_list.h" -#include "dlg/dlg_season.h" - -anime::Database AnimeDatabase; -anime::ImageDatabase ImageDatabase; -anime::SeasonDatabase SeasonDatabase; - -namespace anime { - -// ============================================================================= - -Database::Database() { - folder_ = Taiga.GetDataPath() + L"db\\"; - file_ = L"anime.xml"; -} - -bool Database::LoadDatabase() { - // Initialize - wstring path = folder_ + file_; - - // Load XML file - xml_document doc; - unsigned int options = parse_default & ~parse_eol; - xml_parse_result result = doc.load_file(path.c_str(), options); - if (result.status != status_ok && result.status != status_file_not_found) { - return false; - } - - // Read items - xml_node animedb_node = doc.child(L"animedb"); - for (xml_node node = animedb_node.child(L"anime"); node; node = node.next_sibling(L"anime")) { - int id = XML_ReadIntValue(node, L"series_animedb_id"); - Item& item = items[id]; // Creates the item if it doesn't exist - item.SetId(id); - item.SetTitle(XML_ReadStrValue(node, L"series_title")); - item.SetEnglishTitle(XML_ReadStrValue(node, L"series_english")); - item.SetSynonyms(XML_ReadStrValue(node, L"series_synonyms")); - item.SetType(XML_ReadIntValue(node, L"series_type")); - item.SetEpisodeCount(XML_ReadIntValue(node, L"series_episodes")); - item.SetAiringStatus(XML_ReadIntValue(node, L"series_status")); - item.SetDate(DATE_START, Date(XML_ReadStrValue(node, L"series_start"))); - item.SetDate(DATE_END, Date(XML_ReadStrValue(node, L"series_end"))); - item.SetImageUrl(XML_ReadStrValue(node, L"series_image")); - item.SetGenres(XML_ReadStrValue(node, L"genres")); - item.SetProducers(XML_ReadStrValue(node, L"producers")); - item.SetScore(XML_ReadStrValue(node, L"score")); - item.SetRank(XML_ReadStrValue(node, L"rank")); - item.SetPopularity(XML_ReadStrValue(node, L"popularity")); - item.SetSynopsis(XML_ReadStrValue(node, L"synopsis")); - item.last_modified = _wtoi64(XML_ReadStrValue(node, L"last_modified").c_str()); - } - - return true; -} - -bool Database::SaveDatabase() { - if (items.empty()) return false; - - // Initialize - xml_document doc; - xml_node animedb_node = doc.append_child(L"animedb"); - - // Write items - for (auto it = items.begin(); it != items.end(); ++it) { - xml_node anime_node = animedb_node.append_child(L"anime"); - #define XML_WI(n, v) \ - if (v > 0) XML_WriteIntValue(anime_node, n, v) - #define XML_WS(n, v, t) \ - if (!v.empty()) XML_WriteStrValue(anime_node, n, v.c_str(), t) - XML_WI(L"series_animedb_id", it->second.GetId()); - XML_WS(L"series_title", it->second.GetTitle(), node_cdata); - XML_WS(L"series_english", it->second.GetEnglishTitle(), node_cdata); - XML_WS(L"series_synonyms", Join(it->second.GetSynonyms(), L"; "), node_cdata); - XML_WI(L"series_type", it->second.GetType()); - XML_WI(L"series_episodes", it->second.GetEpisodeCount()); - XML_WI(L"series_status", it->second.GetAiringStatus()); - XML_WS(L"series_start", wstring(it->second.GetDate(DATE_START)), node_pcdata); - XML_WS(L"series_end", wstring(it->second.GetDate(DATE_END)), node_pcdata); - XML_WS(L"series_image", it->second.GetImageUrl(), node_pcdata); - XML_WS(L"genres", it->second.GetGenres(), node_pcdata); - XML_WS(L"producers", it->second.GetProducers(), node_pcdata); - XML_WS(L"score", it->second.GetScore(), node_pcdata); - XML_WS(L"rank", it->second.GetRank(), node_pcdata); - XML_WS(L"popularity", it->second.GetPopularity(), node_pcdata); - XML_WS(L"synopsis", it->second.GetSynopsis(), node_cdata), node_pcdata; - XML_WS(L"last_modified", ToWstr(it->second.last_modified), node_pcdata); - #undef XML_WS - #undef XML_WI - } - - // Save - CreateFolder(folder_); - wstring file = folder_ + file_; - return doc.save_file(file.c_str(), L"\x09", format_default | format_write_bom); -} - -Item* Database::FindItem(int anime_id) { - if (anime_id > ID_UNKNOWN) { - auto it = items.find(anime_id); - if (it != items.end()) return &it->second; - } - - return nullptr; -} - -Item* Database::FindSequel(int anime_id) { - int sequel_id = ID_UNKNOWN; - - switch (anime_id) { - // Gintama -> Gintama' - case 918: sequel_id = 9969; break; - // Tegami Bachi -> Tegami Bachi Reverse - case 6444: sequel_id = 8311; break; - // Fate/Zero -> Fate/Zero 2nd Season - case 10087: sequel_id = 11741; break; - // Towa no Qwon - case 10294: sequel_id = 10713; break; - case 10713: sequel_id = 10714; break; - case 10714: sequel_id = 10715; break; - case 10715: sequel_id = 10716; break; - case 10716: sequel_id = 10717; break; - } - - return FindItem(sequel_id); -} - -void Database::UpdateItem(Item& new_item) { - critical_section_.Enter(); - - Item* item = FindItem(new_item.GetId()); - if (!item) { - // Add a new item - item = &items[new_item.GetId()]; - } - - // Update series information if new information is, well, new. - if (!item->last_modified || new_item.last_modified > item->last_modified) { - item->SetId(new_item.GetId()); - item->last_modified = new_item.last_modified; - - // Update only if a value is non-empty - if (new_item.GetType() > 0) - item->SetType(new_item.GetType()); - if (new_item.GetEpisodeCount(false) > -1) - item->SetEpisodeCount(new_item.GetEpisodeCount()); - if (new_item.GetAiringStatus(false) > 0) - item->SetAiringStatus(new_item.GetAiringStatus()); - if (!new_item.GetTitle().empty()) - item->SetTitle(new_item.GetTitle()); - if (!new_item.GetEnglishTitle(false).empty()) - item->SetEnglishTitle(new_item.GetEnglishTitle()); - if (!new_item.GetSynonyms().empty()) - item->SetSynonyms(new_item.GetSynonyms()); - if (mal::IsValidDate(new_item.GetDate(DATE_START))) - item->SetDate(DATE_START, new_item.GetDate(DATE_START)); - if (mal::IsValidDate(new_item.GetDate(DATE_END))) - item->SetDate(DATE_END, new_item.GetDate(DATE_END)); - if (!new_item.GetImageUrl().empty()) - item->SetImageUrl(new_item.GetImageUrl()); - if (!new_item.GetGenres().empty()) - item->SetGenres(new_item.GetGenres()); - if (!new_item.GetPopularity().empty()) - item->SetPopularity(new_item.GetPopularity()); - if (!new_item.GetProducers().empty()) - item->SetProducers(new_item.GetProducers()); - if (!new_item.GetRank().empty()) - item->SetRank(new_item.GetRank()); - if (!new_item.GetScore().empty()) - item->SetScore(new_item.GetScore()); - if (!new_item.GetSynopsis().empty()) - item->SetSynopsis(new_item.GetSynopsis()); - - // Update clean titles, if necessary - if (!new_item.GetTitle().empty() || - !new_item.GetSynonyms().empty() || - !new_item.GetEnglishTitle(false).empty()) - Meow.UpdateCleanTitles(new_item.GetId()); - } - - // Update user information - if (new_item.IsInList()) { - // Make sure our pointer to MyInformation class is valid - item->AddtoUserList(); - - item->SetMyLastWatchedEpisode(new_item.GetMyLastWatchedEpisode(false)); - item->SetMyScore(new_item.GetMyScore(false)); - item->SetMyStatus(new_item.GetMyStatus(false)); - item->SetMyRewatching(new_item.GetMyRewatching(false)); - item->SetMyRewatchingEp(new_item.GetMyRewatchingEp()); - item->SetMyDate(DATE_START, new_item.GetMyDate(DATE_START)); - item->SetMyDate(DATE_END, new_item.GetMyDate(DATE_END)); - item->SetMyLastUpdated(new_item.GetMyLastUpdated()); - item->SetMyTags(new_item.GetMyTags(false)); - - user.IncreaseItemCount(item->GetMyStatus(false), false); - } - - critical_section_.Leave(); -} - -// ============================================================================= - -void Database::ClearUserData() { - AnimeListDialog.SetCurrentId(ID_UNKNOWN); - - for (auto it = items.begin(); it != items.end(); ++it) { - it->second.RemoveFromUserList(); - } - - user.Clear(); -} - -void Database::ClearInvalidItems() { - for (auto it = items.begin(); it != items.end(); ) { - if (!it->second.GetId() || it->first != it->second.GetId()) { - LOG(LevelDebug, L"ID: " + ToWstr(it->first)); - items.erase(it++); - } else { - ++it; - } - } -} - -bool Database::LoadList(bool set_last_modified) { - // Initialize - ClearUserData(); - if (Settings.Account.MAL.user.empty()) return false; - wstring file = Taiga.GetDataPath() + - L"user\\" + Settings.Account.MAL.user + L"\\anime.xml"; - time_t last_modified = set_last_modified ? time(nullptr) : 0; - - // Load XML file - xml_document doc; - xml_parse_result result = doc.load_file(file.c_str()); - if (result.status != status_ok && result.status != status_file_not_found) { - MessageBox(NULL, L"Could not read anime list.", file.c_str(), MB_OK | MB_ICONERROR); - return false; - } - - // Read user info - xml_node myanimelist = doc.child(L"myanimelist"); - xml_node myinfo = myanimelist.child(L"myinfo"); - user.SetId(XML_ReadIntValue(myinfo, L"user_id")); - user.SetName(XML_ReadStrValue(myinfo, L"user_name")); - // Since MAL can be too slow to update these values, we'll be counting by - // ourselves at Database::UpdateItem(). - /* - user.SetItemCount(mal::MYSTATUS_WATCHING, - XML_ReadIntValue(myinfo, L"user_watching"), false); - user.SetItemCount(mal::MYSTATUS_COMPLETED, - XML_ReadIntValue(myinfo, L"user_completed"), false); - user.SetItemCount(mal::MYSTATUS_ONHOLD, - XML_ReadIntValue(myinfo, L"user_onhold"), false); - user.SetItemCount(mal::MYSTATUS_DROPPED, - XML_ReadIntValue(myinfo, L"user_dropped"), false); - user.SetItemCount(mal::MYSTATUS_PLANTOWATCH, - XML_ReadIntValue(myinfo, L"user_plantowatch"), false); - */ - user.SetDaysSpentWatching(XML_ReadStrValue(myinfo, L"user_days_spent_watching")); - - // Read anime list - for (xml_node node = myanimelist.child(L"anime"); node; node = node.next_sibling(L"anime")) { - Item anime_item; - anime_item.SetId(XML_ReadIntValue(node, L"series_animedb_id")); - anime_item.SetTitle(XML_ReadStrValue(node, L"series_title")); - anime_item.SetSynonyms(XML_ReadStrValue(node, L"series_synonyms")); - anime_item.SetType(XML_ReadIntValue(node, L"series_type")); - anime_item.SetEpisodeCount(XML_ReadIntValue(node, L"series_episodes")); - anime_item.SetAiringStatus(XML_ReadIntValue(node, L"series_status")); - anime_item.SetDate(DATE_START, XML_ReadStrValue(node, L"series_start")); - anime_item.SetDate(DATE_END, XML_ReadStrValue(node, L"series_end")); - anime_item.SetImageUrl(XML_ReadStrValue(node, L"series_image")); - anime_item.last_modified = last_modified; - anime_item.AddtoUserList(); - anime_item.SetMyLastWatchedEpisode(XML_ReadIntValue(node, L"my_watched_episodes")); - anime_item.SetMyDate(DATE_START, XML_ReadStrValue(node, L"my_start_date")); - anime_item.SetMyDate(DATE_END, XML_ReadStrValue(node, L"my_finish_date")); - anime_item.SetMyScore(XML_ReadIntValue(node, L"my_score")); - anime_item.SetMyStatus(XML_ReadIntValue(node, L"my_status")); - anime_item.SetMyRewatching(XML_ReadIntValue(node, L"my_rewatching")); - anime_item.SetMyRewatchingEp(XML_ReadIntValue(node, L"my_rewatching_ep")); - anime_item.SetMyLastUpdated(XML_ReadStrValue(node, L"my_last_updated")); - anime_item.SetMyTags(XML_ReadStrValue(node, L"my_tags")); - UpdateItem(anime_item); - } - - return true; -} - -bool Database::SaveList(int anime_id, const wstring& child, const wstring& value, ListSaveMode mode) { - auto item = FindItem(anime_id); - - if (mode != EDIT_USER && !item) { - return false; - } - - // Initialize - wstring folder = Taiga.GetDataPath() + L"user\\" + Settings.Account.MAL.user + L"\\"; - wstring file = folder + L"anime.xml"; - if (!PathExists(folder)) CreateFolder(folder); - - // Load XML file - xml_document doc; - xml_parse_result result = doc.load_file(file.c_str()); - if (result.status != status_ok) return false; - - // Read anime list - xml_node myanimelist = doc.child(L"myanimelist"); - switch (mode) { - // Add anime item - case ADD_ANIME: { - xml_node node = myanimelist.append_child(L"anime"); - XML_WriteIntValue(node, L"series_animedb_id", item->GetId()); - XML_WriteIntValue(node, L"my_watched_episodes", item->GetMyLastWatchedEpisode(false)); - XML_WriteStrValue(node, L"my_start_date", wstring(item->GetMyDate(DATE_START)).c_str()); - XML_WriteStrValue(node, L"my_finish_date", wstring(item->GetMyDate(DATE_END)).c_str()); - XML_WriteIntValue(node, L"my_score", item->GetMyScore(false)); - XML_WriteIntValue(node, L"my_status", item->GetMyStatus(false)); - XML_WriteIntValue(node, L"my_rewatching", item->GetMyRewatching(false)); - XML_WriteIntValue(node, L"my_rewatching_ep", item->GetMyRewatchingEp()); - XML_WriteStrValue(node, L"my_last_updated", item->GetMyLastUpdated().c_str()); - XML_WriteStrValue(node, L"my_tags", item->GetMyTags(false).c_str()); - doc.save_file(file.c_str(), L"\x09", format_default | format_write_bom); - return true; - } - // Delete anime item - case DELETE_ANIME: { - for (xml_node node = myanimelist.child(L"anime"); node; node = node.next_sibling(L"anime")) { - if (XML_ReadIntValue(node, L"series_animedb_id") == item->GetId()) { - myanimelist.remove_child(node); - doc.save_file(file.c_str(), L"\x09", format_default | format_write_bom); - return true; - } - } - break; - } - // Edit anime data - case EDIT_ANIME: { - for (xml_node node = myanimelist.child(L"anime"); node; node = node.next_sibling(L"anime")) { - if (XML_ReadIntValue(node, L"series_animedb_id") == item->GetId()) { - xml_node child_node = node.child(child.c_str()); - if (wstring(child_node.first_child().value()).empty()) { - child_node = child_node.append_child(node_pcdata); - } else { - child_node = child_node.first_child(); - } - child_node.set_value(value.c_str()); - doc.save_file(file.c_str(), L"\x09", format_default | format_write_bom); - return true; - } - } - break; - } - // Edit user data - case EDIT_USER: { - myanimelist.child(L"myinfo").child(child.c_str()).first_child().set_value(value.c_str()); - doc.save_file(file.c_str(), L"\x09", format_default | format_write_bom); - return true; - } - } - - return false; -} - -// ============================================================================= - -bool Database::DeleteListItem(int anime_id) { - auto item = FindItem(anime_id); - if (!item) return false; - if (!item->IsInList()) return false; - - user.DecreaseItemCount(item->GetMyStatus(false), true); - SaveList(anime_id, L"", L"", DELETE_ANIME); - item->RemoveFromUserList(); - - return true; -} - -// ============================================================================= - -FansubDatabase::FansubDatabase() { - file_ = L"fansub.xml"; - folder_ = Taiga.GetDataPath() + L"db\\"; -} - -bool FansubDatabase::Load() { - // Initialize - wstring file = folder_ + file_; - items.clear(); - - // Load XML file - xml_document doc; - xml_parse_result result = doc.load_file(file.c_str()); - if (result.status != status_ok && result.status != status_file_not_found) { - MessageBox(NULL, L"Could not read fansub data.", file.c_str(), MB_OK | MB_ICONERROR); - return false; - } - - // Read items - xml_node fansub_node = doc.child(L"fansub_groups"); - for (xml_node node = fansub_node.child(L"fansub"); node; node = node.next_sibling(L"fansub")) { - items.push_back(XML_ReadStrValue(node, L"name")); - } - - return true; -} - -void FansubDatabase::Save() { - // TODO -} - -// ============================================================================= - -bool ImageDatabase::Load(int anime_id, bool load, bool download) { - if (anime_id <= anime::ID_UNKNOWN) - return false; - - if (items_.find(anime_id) != items_.end()) { - if (items_[anime_id].data > anime::ID_UNKNOWN) { - return true; - } else if (!load) { - return false; - } - } - - if (items_[anime_id].Load(anime::GetImagePath(anime_id))) { - items_[anime_id].data = anime_id; - if (download) { - // Refresh if current file is too old - auto anime_item = AnimeDatabase.FindItem(anime_id); - if (anime_item->GetAiringStatus() != mal::STATUS_FINISHED) { - // Check last modified date (>= 7 days) - if (GetFileAge(anime::GetImagePath(anime_id)) / (60 * 60 * 24) >= 7) { - mal::DownloadImage(anime_id, anime_item->GetImageUrl()); - } - } - } - return true; - } else { - items_[anime_id].data = -1; - } - - if (download) { - auto anime_item = AnimeDatabase.FindItem(anime_id); - mal::DownloadImage(anime_id, anime_item->GetImageUrl()); - } - - return false; -} - -void ImageDatabase::FreeMemory() { - for (auto it = ::AnimeDatabase.items.begin(); it != ::AnimeDatabase.items.end(); ++it) { - bool erase = true; - int anime_id = it->first; - - if (items_.find(anime_id) == items_.end()) - continue; - - if (::AnimeDialog.GetCurrentId() == anime_id || - ::NowPlayingDialog.GetCurrentId() == anime_id) - erase = false; - - if (!::SeasonDatabase.items.empty()) - if (std::find(::SeasonDatabase.items.begin(), ::SeasonDatabase.items.end(), - anime_id) != ::SeasonDatabase.items.end()) - if (SeasonDialog.IsVisible()) - erase = false; - - if (erase) - items_.erase(anime_id); - } -} - -Image* ImageDatabase::GetImage(int anime_id) { - if (items_.find(anime_id) != items_.end()) - if (items_[anime_id].data > 0) - return &items_[anime_id]; - return nullptr; -} - -// ============================================================================= - -SeasonDatabase::SeasonDatabase() { - folder_ = Taiga.GetDataPath() + L"db\\season\\"; -} - -bool SeasonDatabase::Load(wstring file) { - // Initialize - file_ = file; - file = folder_ + file; - items.clear(); - - // Load XML file - xml_document doc; - xml_parse_result result = doc.load_file(file.c_str()); - if (result.status != status_ok && result.status != status_file_not_found) { - MessageBox(NULL, L"Could not read season data.", file.c_str(), MB_OK | MB_ICONERROR); - return false; - } - - // Read information - xml_node season_node = doc.child(L"season"); - name = XML_ReadStrValue(season_node.child(L"info"), L"name"); - time_t last_modified = _wtoi64(XML_ReadStrValue(season_node.child(L"info"), L"last_modified").c_str()); - - // Read items - for (xml_node node = season_node.child(L"anime"); node; node = node.next_sibling(L"anime")) { - int anime_id = XML_ReadIntValue(node, L"series_animedb_id"); - items.push_back(anime_id); - - auto anime_item = AnimeDatabase.FindItem(anime_id); - if (anime_item && anime_item->last_modified >= last_modified) - continue; - - Item item; - item.SetId(XML_ReadIntValue(node, L"series_animedb_id")); - item.SetTitle(XML_ReadStrValue(node, L"series_title")); - item.SetType(XML_ReadIntValue(node, L"series_type")); - item.SetImageUrl(XML_ReadStrValue(node, L"series_image")); - item.SetProducers(XML_ReadStrValue(node, L"producers")); - xml_node settings_node = node.child(L"settings"); - item.keep_title = XML_ReadIntValue(settings_node, L"keep_title") != 0; - item.last_modified = last_modified; - AnimeDatabase.UpdateItem(item); - } - - return true; -} - -bool SeasonDatabase::IsRefreshRequired() { - int count = 0; - bool required = false; - - for (size_t i = 0; i < items.size(); i++) { - int anime_id = items.at(i); - auto anime_item = AnimeDatabase.FindItem(anime_id); - if (anime_item) { - const Date& date_start = anime_item->GetDate(anime::DATE_START); - if (!mal::IsValidDate(date_start) || anime_item->GetSynopsis().empty()) - count++; - } - if (count > 20) { - required = true; - break; - } - } - - return required; -} - -void SeasonDatabase::Review(bool hide_hentai) { - Date date_start, date_end; - mal::GetSeasonInterval(name, date_start, date_end); - - // Check for invalid items - for (size_t i = 0; i < items.size(); i++) { - bool invalid = false; - int anime_id = items.at(i); - auto anime_item = AnimeDatabase.FindItem(anime_id); - if (anime_item) { - // Airing date must be within the interval - const Date& anime_start = anime_item->GetDate(anime::DATE_START); - if (mal::IsValidDate(anime_start)) - if (anime_start < date_start || anime_start > date_end) - invalid = true; - // TODO: Filter by rating instead if made possible in API - if (hide_hentai && InStr(anime_item->GetGenres(), L"Hentai", 0, true) > -1) - invalid = true; - if (invalid) { - items.erase(items.begin() + i--); - LOG(LevelDebug, L"Removed item: \"" + anime_item->GetTitle() + - L"\" (" + wstring(anime_start) + L")"); - } - } - } - - // Check for missing items - for (auto it = AnimeDatabase.items.begin(); it != AnimeDatabase.items.end(); ++it) { - if (std::find(items.begin(), items.end(), it->second.GetId()) != items.end()) - continue; - // TODO: Filter by rating instead if made possible in API - if (hide_hentai && InStr(it->second.GetGenres(), L"Hentai", 0, true) > -1) - continue; - // Airing date must be within the interval - const Date& anime_start = it->second.GetDate(anime::DATE_START); - if (anime_start.year && anime_start.month && - anime_start >= date_start && anime_start <= date_end) { - items.push_back(it->second.GetId()); - LOG(LevelDebug, L"Added item: \"" + it->second.GetTitle() + - L"\" (" + wstring(anime_start) + L")"); - } - } -} - -} // namespace anime \ No newline at end of file diff --git a/anime_db.h b/anime_db.h deleted file mode 100644 index 0aba0a501..000000000 --- a/anime_db.h +++ /dev/null @@ -1,167 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef ANIMEDB_H -#define ANIMEDB_H - -#include "std.h" - -#include "anime_item.h" -#include "anime_user.h" -#include "gfx.h" - -#include "win32/win_thread.h" - -namespace anime { - -// ============================================================================= - -enum ListSaveMode { - ADD_ANIME, - DELETE_ANIME, - EDIT_ANIME, - EDIT_USER -}; - -class Database { - public: - Database(); - virtual ~Database() {} - - // Loads local anime database on program startup from db/anime.xml, returns - // false if no such database exists. - bool LoadDatabase(); - // Saves local anime database on program exit to db/anime.xml, returns false - // if the database is empty. - bool SaveDatabase(); - - // Deletes invalid anime items. - void ClearInvalidItems(); - // Deletes all user information from anime items. - void ClearUserData(); - // Loads anime list on startup and list-refresh from - // user\\anime.xml, returns false if no such list exists. - // last_modified values of all list items are set to current time if - // set_last_modified is true. - bool LoadList(bool set_last_modified = false); - // Saves anime list everytime an item is updated to - // user\\anime.xml, returns false if no such item exists with given - // ID in the database or the relevant XML node doesn't exist. - bool SaveList(int anime_id, - const wstring& child, const wstring& value, - ListSaveMode mode = EDIT_ANIME); - - // Searches the database for an item with given ID. - Item* FindItem(int anime_id); - // Searches the database for an item with given ID, which has a sequel. - Item* FindSequel(int anime_id); - // Updates anime information, or adds a new item if no such anime exists. - // New information may include both series and user information. Series - // information is updated depending on its last_modified value. - void UpdateItem(Item& item); - - // Deletes user information from an item, after HTTP_MAL_AnimeDelete - // succeeds. - bool DeleteListItem(int anime_id); - - // Anime items are mapped to their IDs. - std::map items; - - // Read from user\\anime.xml. - ListUser user; - - private: - // Thread safety - win32::CriticalSection critical_section_; - - wstring file_; - wstring folder_; -}; - -class FansubDatabase { - public: - FansubDatabase(); - virtual ~FansubDatabase() {} - - // Loads fansub data from db\fansub.xml, returns false if no such file exists. - bool Load(); - - // Saves fansub data to db\fansub.xml. - void Save(); - - std::list items; - - private: - wstring file_; - wstring folder_; -}; - -class ImageDatabase { - public: - ImageDatabase() {} - virtual ~ImageDatabase() {} - - // Loads a picture into memory, downloads a new file if requested. - bool Load(int anime_id, bool load, bool download); - - // Releases image data from memory if an image is not in sight. - void FreeMemory(); - - // Returns a pointer to requested image if available. - Image* GetImage(int anime_id); - - private: - std::map items_; -}; - -class SeasonDatabase { - public: - SeasonDatabase(); - virtual ~SeasonDatabase() {} - - // Loads season data from db\season\.xml, returns false if no such - // file exists. - bool Load(wstring file); - - // Checkes if a significant portion of season data is empty and requires - // refreshing. - bool IsRefreshRequired(); - - // Improves season data by excluding invalid items (i.e. postpones series) and - // adding missing ones from the anime database. - void Review(bool hide_hentai = true); - - // Only IDs are stored here, actual info is kept in Database. - vector items; - - // Season name (e.g. Spring 2012) - wstring name; - - private: - wstring file_; - wstring folder_; -}; - -} // namespace anime - -// Global objects -extern anime::Database AnimeDatabase; -extern anime::ImageDatabase ImageDatabase; -extern anime::SeasonDatabase SeasonDatabase; - -#endif // ANIMEDB_H \ No newline at end of file diff --git a/anime_item.cpp b/anime_item.cpp deleted file mode 100644 index 52203ffa3..000000000 --- a/anime_item.cpp +++ /dev/null @@ -1,759 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "anime_db.h" -#include "anime_episode.h" -#include "anime_item.h" - -#include "common.h" -#include "history.h" -#include "logger.h" -#include "myanimelist.h" -#include "settings.h" -#include "string.h" -#include "taiga.h" -#include "time.h" - -#include "dlg/dlg_anime_list.h" -#include "dlg/dlg_main.h" -#include "dlg/dlg_search.h" - -#include "win32/win_taskbar.h" - -anime::Database* anime::Item::database_ = &AnimeDatabase; - -namespace anime { - -// ============================================================================= - -Item::Item() - : keep_title(false), - last_modified(0), - my_info_(nullptr) { -} - -Item::~Item() { -} - -// ============================================================================= - -int Item::GetId() const { - return series_info_.id; -} - -int Item::GetType() const { - return series_info_.type; -} - -int Item::GetEpisodeCount(bool estimation) const { - // Generally we want an exact number without estimation - if (!estimation || series_info_.episodes > 0) - return series_info_.episodes; - - int number = 0; - - // Estimate using user information - if (IsInList()) - number = max(GetMyLastWatchedEpisode(), GetAvailableEpisodeCount()); - - // Estimate using airing dates of TV series - if (series_info_.type == mal::TYPE_TV) { - Date date_start = GetDate(DATE_START); - if (mal::IsValidDate(date_start)) { - Date date_end = GetDate(DATE_END); - // Use current date in Japan if ending date is unknown - if (!mal::IsValidDate(date_end)) date_end = GetDateJapan(); - // Assuming the series is aired weekly - number = max(number, (date_end - date_start) / 7); - } - } - - // Given all TV series aired since 2000, most them have their episodes - // spanning one or two seasons. Following is a table of top ten values: - // - // Episodes Seasons Percent - // ------------------------------ - // 12 1 23.6% - // 13 1 20.2% - // 26 2 15.4% - // 24 2 6.4% - // 25 2 5.0% - // 52 4 4.4% - // 51 4 3.1% - // 11 1 2.6% - // 50 4 2.3% - // 39 3 1.4% - // ------------------------------ - // Total: 84.6% - // - // With that in mind, we can normalize our output at several points. - if (number < 12) return 13; - if (number < 24) return 26; - if (number < 50) return 52; - - // This is a series that has aired for more than a year, which means we cannot - // estimate for how long it is going to continue. - return 0; -} - -int Item::GetAiringStatus(bool check_date) const { - if (!check_date) return series_info_.status; - if (IsFinishedAiring()) return mal::STATUS_FINISHED; - if (IsAiredYet()) return mal::STATUS_AIRING; - return mal::STATUS_NOTYETAIRED; -} - -const wstring& Item::GetTitle() const { - return series_info_.title; -} - -const wstring& Item::GetEnglishTitle(bool fallback) const { - if (series_info_.english_title.empty() && fallback) - return series_info_.title; - return series_info_.english_title; -} - -const vector& Item::GetSynonyms() const { - return series_info_.synonyms; -} - -const Date& Item::GetDate(DateType type) const { - switch (type) { - case DATE_START: - default: - return series_info_.date_start; - case DATE_END: - return series_info_.date_end; - } -} - -const wstring& Item::GetImageUrl() const { - return series_info_.image_url; -} - -const wstring& Item::GetGenres() const { - return series_info_.genres; -} - -const wstring& Item::GetPopularity() const { - return series_info_.popularity; -} - -const wstring& Item::GetProducers() const { - return series_info_.producers; -} - -const wstring& Item::GetRank() const { - return series_info_.rank; -} - -const wstring& Item::GetScore() const { - return series_info_.score; -} - -const wstring& Item::GetSynopsis() const { - return series_info_.synopsis; -} - -// ============================================================================= - -int Item::GetMyLastWatchedEpisode(bool check_events) const { - if (!my_info_.get()) return 0; - EventItem* event_item = check_events ? - SearchHistory(EVENT_SEARCH_EPISODE) : nullptr; - return event_item ? *event_item->episode : my_info_->watched_episodes; -} - -int Item::GetMyScore(bool check_events) const { - if (!my_info_.get()) return 0; - EventItem* event_item = check_events ? - SearchHistory(EVENT_SEARCH_SCORE) : nullptr; - return event_item ? *event_item->score : my_info_->score; -} - -int Item::GetMyStatus(bool check_events) const { - if (!my_info_.get()) return mal::MYSTATUS_NOTINLIST; - EventItem* event_item = check_events ? - SearchHistory(EVENT_SEARCH_STATUS) : nullptr; - return event_item ? *event_item->status : my_info_->status; -} - -int Item::GetMyRewatching(bool check_events) const { - if (!my_info_.get()) return FALSE; - EventItem* event_item = check_events ? - SearchHistory(EVENT_SEARCH_REWATCH) : nullptr; - return event_item ? *event_item->enable_rewatching : my_info_->rewatching; -} - -int Item::GetMyRewatchingEp() const { - if (!my_info_.get()) return 0; - return my_info_->rewatching_ep; -} - -const Date Item::GetMyDate(DateType type, bool check_events) const { - if (!my_info_.get()) return Date(); - EventItem* event_item = nullptr; - switch (type) { - case DATE_START: - default: - event_item = check_events ? - SearchHistory(EVENT_SEARCH_DATE_START) : nullptr; - return event_item ? - mal::TranslateDateFromApi(*event_item->date_start) : - my_info_->date_start; - case DATE_END: - event_item = check_events ? - SearchHistory(EVENT_SEARCH_DATE_END) : nullptr; - return event_item ? - mal::TranslateDateFromApi(*event_item->date_finish) : - my_info_->date_finish; - } -} - -const wstring Item::GetMyLastUpdated() const { - if (!my_info_.get()) return wstring(); - return my_info_->last_updated; -} - -const wstring Item::GetMyTags(bool check_events) const { - if (!my_info_.get()) return wstring(); - EventItem* event_item = check_events ? - SearchHistory(EVENT_SEARCH_TAGS) : nullptr; - return event_item ? *event_item->tags : my_info_->tags; -} - -// ============================================================================= - -void Item::SetId(int anime_id) { - series_info_.id = anime_id; -} - -void Item::SetType(int type) { - series_info_.type = type; -} - -void Item::SetEpisodeCount(int number) { - series_info_.episodes = number; - - if (number >= 0) - if (static_cast(number) > local_info_.available_episodes.size()) - local_info_.available_episodes.resize(number); -} - -void Item::SetAiringStatus(int status) { - series_info_.status = status; -} - -void Item::SetTitle(const wstring& title) { - series_info_.title = title; -} - -void Item::SetEnglishTitle(const wstring& title) { - series_info_.english_title = title; -} - -void Item::SetSynonyms(const wstring& synonyms) { - vector temp; - Split(synonyms, L"; ", temp); - SetSynonyms(temp); -} - -void Item::SetSynonyms(const vector& synonyms) { - series_info_.synonyms = synonyms; - RemoveEmptyStrings(series_info_.synonyms); -} - -void Item::SetDate(DateType type, const Date& date) { - switch (type) { - case DATE_START: - series_info_.date_start = date; - break; - case DATE_END: - series_info_.date_end = date; - break; - } -} - -void Item::SetImageUrl(const wstring& url) { - series_info_.image_url = url; -} - -void Item::SetGenres(const wstring& genres) { - series_info_.genres = genres; -} - -void Item::SetPopularity(const wstring& popularity) { - series_info_.popularity = popularity; -} - -void Item::SetProducers(const wstring& producers) { - series_info_.producers = producers; -} - -void Item::SetRank(const wstring& rank) { - series_info_.rank = rank; -} - -void Item::SetScore(const wstring& score) { - series_info_.score = score; -} - -void Item::SetSynopsis(const wstring& synopsis) { - series_info_.synopsis = synopsis; -} - -// ============================================================================= - -void Item::SetMyLastWatchedEpisode(int number) { - assert(my_info_.get()); - my_info_->watched_episodes = number; -} - -void Item::SetMyScore(int score) { - assert(my_info_.get()); - my_info_->score = score; -} - -void Item::SetMyStatus(int status) { - assert(my_info_.get()); - my_info_->status = status; -} - -void Item::SetMyRewatching(int rewatching) { - assert(my_info_.get()); - my_info_->rewatching = rewatching; -} - -void Item::SetMyRewatchingEp(int rewatching_ep) { - assert(my_info_.get()); - my_info_->rewatching_ep = rewatching_ep; -} - -void Item::SetMyDate(DateType type, const Date& date) { - assert(my_info_.get()); - switch (type) { - case DATE_START: - my_info_->date_start = date; - break; - case DATE_END: - my_info_->date_finish = date; - break; - } -} - -void Item::SetMyLastUpdated(const wstring& last_updated) { - assert(my_info_.get()); - my_info_->last_updated = last_updated; -} - -void Item::SetMyTags(const wstring& tags) { - assert(my_info_.get()); - my_info_->tags = tags; -} - -// ============================================================================= - -bool Item::IsAiredYet() const { - if (series_info_.status != mal::STATUS_NOTYETAIRED) return true; - if (!mal::IsValidDate(series_info_.date_start)) return false; - - Date date_japan = GetDateJapan(); - Date date_start = series_info_.date_start; - - // Assume the worst case - if (!series_info_.date_start.month) - date_start.month = 12; - if (!series_info_.date_start.day) - date_start.day = 31; - - return date_japan >= date_start; -} - -bool Item::IsFinishedAiring() const { - if (series_info_.status == mal::STATUS_FINISHED) return true; - if (!mal::IsValidDate(series_info_.date_end)) return false; - if (!IsAiredYet()) return false; - return GetDateJapan() > series_info_.date_end; -} - -// ============================================================================= - -bool Item::CheckEpisodes(int number, bool check_folder) { - // Check folder - if (check_folder) - CheckFolder(); - if (GetFolder().empty()) { - for (int i = 1; i <= GetAvailableEpisodeCount(); i++) - SetEpisodeAvailability(i, false, L""); - return false; - } - - // Check all episodes - if (number == -1) { - SearchFileFolder(*this, GetFolder(), -1, false); - return true; - - // Check single episode - } else { - if (number == 0) { - if (IsNewEpisodeAvailable()) return true; - SetNewEpisodePath(L""); - number = GetEpisodeCount() == 1 ? 0 : GetMyLastWatchedEpisode() + 1; - } - wstring file = SearchFileFolder(*this, GetFolder(), number, false); - return !file.empty(); - } -} - -int Item::GetAvailableEpisodeCount() const { - return static_cast(local_info_.available_episodes.size()); -} - -int Item::GetLastAiredEpisodeNumber(bool estimate) { - if (local_info_.last_aired_episode) - return local_info_.last_aired_episode; - - // No need to estimate if the series isn't currently airing - switch (GetAiringStatus()) { - case mal::STATUS_FINISHED: - local_info_.last_aired_episode = GetEpisodeCount(); - return local_info_.last_aired_episode; - case mal::STATUS_NOTYETAIRED: - case mal::STATUS_UNKNOWN: - return 0; - } - - if (!estimate) - return 0; - - // Can't estimate for other types of anime - if (GetType() != mal::TYPE_TV) - return 0; - - // TV series air weekly, so the number of weeks that has passed since the day - // the series started airing gives us the last aired episode. Note that - // irregularities such as broadcasts being postponed due to sports events make - // this method unreliable. - const Date& date_start = GetDate(anime::DATE_START); - if (date_start.year && date_start.month && date_start.day) { - // To compensate for the fact that we don't know the airing hour, - // we substract one more day. - int date_diff = GetDateJapan() - date_start - 1; - if (date_diff > -1) { - int number_of_weeks = date_diff / 7; - if (number_of_weeks < GetEpisodeCount()) { - local_info_.last_aired_episode = number_of_weeks + 1; - } else { - local_info_.last_aired_episode = GetEpisodeCount(); - } - } - } - - return local_info_.last_aired_episode; -} - -wstring Item::GetNewEpisodePath() const { - return local_info_.new_episode_path; -} - -bool Item::IsEpisodeAvailable(int number) const { - if (number < 1) number = 1; - if (static_cast(number) > local_info_.available_episodes.size()) - return false; - - return local_info_.available_episodes.at(number - 1); -} - -bool Item::IsNewEpisodeAvailable() const { - return IsEpisodeAvailable(GetMyLastWatchedEpisode() + 1); -} - -bool Item::PlayEpisode(int number) { - if (number > GetEpisodeCount() && GetEpisodeCount() != 0) - return false; - - wstring file_path; - - SetSharedCursor(IDC_WAIT); - - // Check saved episode path - if (number == GetMyLastWatchedEpisode() + 1) - if (!GetNewEpisodePath().empty()) - if (FileExists(GetNewEpisodePath())) - file_path = GetNewEpisodePath(); - - // Check anime folder - if (file_path.empty()) { - CheckFolder(); - if (!GetFolder().empty()) { - file_path = SearchFileFolder(*this, GetFolder(), number, false); - } - } - - // Check other folders - if (file_path.empty()) { - for (auto it = Settings.Folders.root.begin(); - file_path.empty() && it != Settings.Folders.root.end(); ++it) { - file_path = SearchFileFolder(*this, *it, number, false); - } - } - - if (file_path.empty()) { - if (number == 0) number = 1; - MainDialog.ChangeStatus( - L"Could not find episode #" + ToWstr(number) + L" (" + GetTitle() + L")."); - } else { - Execute(file_path); - } - - SetSharedCursor(IDC_ARROW); - - return !file_path.empty(); -} - -bool Item::SetEpisodeAvailability(int number, bool available, const wstring& path) { - if (number == 0) number = 1; - - if (number <= GetEpisodeCount() || GetEpisodeCount() == 0) { - if (static_cast(number) > local_info_.available_episodes.size()) { - local_info_.available_episodes.resize(number); - } - local_info_.available_episodes.at(number - 1) = available; - if (number == GetMyLastWatchedEpisode() + 1) { - SetNewEpisodePath(path); - } - int list_index = AnimeListDialog.GetListIndex(GetId()); - if (list_index > -1) { - AnimeListDialog.listview.RedrawItems(list_index, list_index, true); - } - return true; - } - - return false; -} - -void Item::SetLastAiredEpisodeNumber(int number) { - if (number > local_info_.last_aired_episode) - local_info_.last_aired_episode = number; -} - -void Item::SetNewEpisodePath(const wstring& path) { - local_info_.new_episode_path = path; -} - -// ============================================================================= - -bool Item::CheckFolder() { - // Check if current folder still exists - if (!GetFolder().empty() && !FolderExists(GetFolder())) { - LOG(LevelWarning, L"Folder doesn't exist anymore."); - LOG(LevelWarning, L"Path: " + GetFolder()); - SetFolder(L""); - } - - // Search root folders - if (GetFolder().empty()) { - wstring new_folder; - for (auto it = Settings.Folders.root.begin(); - it != Settings.Folders.root.end(); ++it) { - new_folder = SearchFileFolder(*this, *it, 0, true); - if (!new_folder.empty()) { - SetFolder(new_folder); - Settings.Save(); - break; - } - } - } - - return !GetFolder().empty(); -} - -const wstring Item::GetFolder() const { - return local_info_.folder; -} - -void Item::SetFolder(const wstring& folder) { - local_info_.folder = folder; -} - -// ============================================================================= - -bool Item::GetPlaying() const { - return local_info_.playing; -} - -void Item::SetPlaying(bool playing) { - local_info_.playing = playing; -} - -// ============================================================================= - -const vector& Item::GetUserSynonyms() const { - return local_info_.synonyms; -} - -void Item::SetUserSynonyms(const wstring& synonyms) { - vector temp; - Split(synonyms, L"; ", temp); - SetUserSynonyms(temp); -} - -void Item::SetUserSynonyms(const vector& synonyms) { - local_info_.synonyms = synonyms; - RemoveEmptyStrings(local_info_.synonyms); - - if (!synonyms.empty() && CurrentEpisode.anime_id == anime::ID_NOTINLIST) { - CurrentEpisode.Set(anime::ID_UNKNOWN); - } -} - -bool Item::UserSynonymsAvailable() const { - return !local_info_.synonyms.empty(); -} - -bool Item::GetUseAlternative() const { - return local_info_.use_alternative; -} - -void Item::SetUseAlternative(bool use_alternative) { - local_info_.use_alternative = use_alternative; -} - -// ============================================================================= - -void Item::AddtoUserList() { - if (!my_info_.get()) { - my_info_.reset(new MyInformation); - } -} - -bool Item::IsInList() const { - return my_info_.get() && GetMyStatus() != mal::MYSTATUS_NOTINLIST; -} - -void Item::RemoveFromUserList() { - assert(my_info_.use_count() <= 1); - my_info_.reset(); - assert(my_info_.use_count() == 0); -} - -bool Item::IsOldEnough() const { - if (!last_modified) return true; - - time_t time_diff = time(nullptr) - last_modified; - - if (GetAiringStatus() == mal::STATUS_FINISHED) { - return time_diff >= 60 * 60 * 24 * 7; // 1 week - } else { - return time_diff >= 60 * 60; // 1 hour - } -} - -// ============================================================================= - -bool Item::Edit(EventItem& item, const wstring& data, int status_code) { - if (!mal::UpdateSucceeded(item, data, status_code)) { - // Show balloon tip on failure - wstring text = L"Title: " + GetTitle(); - text += L"\nReason: " + (item.reason.empty() ? L"Unknown" : item.reason); - text += L"\nClick to try again."; - Taiga.current_tip_type = TIPTYPE_UPDATEFAILED; - Taskbar.Tip(L"", L"", 0); - Taskbar.Tip(text.c_str(), L"Update failed", NIIF_ERROR); - return false; - } - - // Edit episode - if (item.episode) { - SetMyLastWatchedEpisode(*item.episode); - database_->SaveList(GetId(), L"my_watched_episodes", ToWstr(*item.episode)); - } - // Edit score - if (item.score) { - SetMyScore(*item.score); - database_->SaveList(GetId(), L"my_score", ToWstr(*item.score)); - } - // Edit status - if (item.status) { - database_->user.IncreaseItemCount(*item.status, false); - database_->user.DecreaseItemCount(GetMyStatus(false), true); - SetMyStatus(*item.status); - database_->SaveList(GetId(), L"my_status", ToWstr(*item.status)); - } - // Edit re-watching status - if (item.enable_rewatching) { - SetMyRewatching(*item.enable_rewatching); - database_->SaveList(GetId(), L"my_rewatching", ToWstr(*item.enable_rewatching)); - } - // Edit ID (Add) - if (item.mode == HTTP_MAL_AnimeAdd) { - if (IsNumeric(data)) { - database_->SaveList(GetId(), L"my_id", data); // deprecated - } - } - // Edit tags - if (item.tags) { - SetMyTags(*item.tags); - database_->SaveList(GetId(), L"my_tags", *item.tags); - } - // Edit dates - if (item.date_start) { - Date date_start = mal::TranslateDateFromApi(*item.date_start); - SetMyDate(anime::DATE_START, date_start); - database_->SaveList(GetId(), L"my_start_date", date_start, EDIT_ANIME); - } - if (item.date_finish) { - Date date_finish = mal::TranslateDateFromApi(*item.date_finish); - SetMyDate(anime::DATE_END, date_finish); - database_->SaveList(GetId(), L"my_finish_date", date_finish, EDIT_ANIME); - } - // Delete - if (item.mode == HTTP_MAL_AnimeDelete) { - MainDialog.ChangeStatus(L"Item deleted. (" + GetTitle() + L")"); - database_->DeleteListItem(GetId()); - AnimeListDialog.RefreshList(); - AnimeListDialog.RefreshTabs(); - SearchDialog.RefreshList(); - if (CurrentEpisode.anime_id == item.anime_id) { - CurrentEpisode.Set(anime::ID_NOTINLIST); - } - } - - // Remove item from event queue - History.queue.Remove(); - // Check for more events - History.queue.Check(false); - - // Redraw main list item - int list_index = AnimeListDialog.GetListIndex(GetId()); - if (list_index > -1) { - AnimeListDialog.listview.RedrawItems(list_index, list_index, true); - } - - return true; -} - -// ============================================================================= - -EventItem* Item::SearchHistory(int search_mode) const { - return History.queue.FindItem(series_info_.id, search_mode); -} - -} // namespace anime \ No newline at end of file diff --git a/anime_item.h b/anime_item.h deleted file mode 100644 index afa888089..000000000 --- a/anime_item.h +++ /dev/null @@ -1,182 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef ANIME_ITEM_H -#define ANIME_ITEM_H - -#include "std.h" - -#include "anime.h" - -namespace anime { -class Database; -class Episode; -class Item; -} -class Date; -class EventItem; - -namespace anime { - -class Item { - public: - Item(); - virtual ~Item(); - - // Accessors for series information - int GetId() const; - int GetType() const; - int GetEpisodeCount(bool estimation = false) const; - int GetAiringStatus(bool check_date = true) const; - const wstring& GetTitle() const; - const wstring& GetEnglishTitle(bool fallback = false) const; - const vector& GetSynonyms() const; - const Date& GetDate(DateType type) const; - const wstring& GetImageUrl() const; - const wstring& GetGenres() const; - const wstring& GetPopularity() const; - const wstring& GetProducers() const; - const wstring& GetRank() const; - const wstring& GetScore() const; - const wstring& GetSynopsis() const; - - // Accessors for user information - if check_events flag is enabled, the - // latest relevant value in event queue is returned. - int GetMyLastWatchedEpisode(bool check_events = true) const; - int GetMyScore(bool check_events = true) const; - int GetMyStatus(bool check_events = true) const; - int GetMyRewatching(bool check_events = true) const; - int GetMyRewatchingEp() const; - const Date GetMyDate(DateType type, bool check_events = true) const; - const wstring GetMyLastUpdated() const; - const wstring GetMyTags(bool check_events = true) const; - - // Mutators for series information - void SetId(int anime_id); - void SetType(int type); - void SetEpisodeCount(int number); - void SetAiringStatus(int status); - void SetTitle(const wstring& title); - void SetEnglishTitle(const wstring& title); - void SetSynonyms(const wstring& synonyms); - void SetSynonyms(const vector& synonyms); - void SetDate(DateType type, const Date& date); - void SetImageUrl(const wstring& url); - void SetGenres(const wstring& genres); - void SetPopularity(const wstring& popularity); - void SetProducers(const wstring& producers); - void SetRank(const wstring& rank); - void SetScore(const wstring& score); - void SetSynopsis(const wstring& synopsis); - - // Mutators for user information - void SetMyLastWatchedEpisode(int number); - void SetMyScore(int score); - void SetMyStatus(int status); - void SetMyRewatching(int rewatching); - void SetMyRewatchingEp(int rewatching_ep); - void SetMyDate(DateType type, const Date& date); - void SetMyLastUpdated(const wstring& last_updated); - void SetMyTags(const wstring& tags); - - // Functions related to airing status - bool IsAiredYet() const; - bool IsFinishedAiring() const; - - // Functions related to episode availability - bool CheckEpisodes(int number = -1, bool check_folder = false); - int GetAvailableEpisodeCount() const; - int GetLastAiredEpisodeNumber(bool estimate = false); - wstring GetNewEpisodePath() const; - bool IsEpisodeAvailable(int number) const; - bool IsNewEpisodeAvailable() const; - bool PlayEpisode(int number); - bool SetEpisodeAvailability(int number, bool available, const wstring& path); - void SetLastAiredEpisodeNumber(int number); - void SetNewEpisodePath(const wstring& path); - - // For anime-specific folders on user's computer - bool CheckFolder(); - const wstring GetFolder() const; - void SetFolder(const wstring& folder); - - // More than one anime may have their playing flag on. - bool GetPlaying() const; - void SetPlaying(bool playing); - - // For alternative titles provided by user - const vector& GetUserSynonyms() const; - void SetUserSynonyms(const wstring& synonyms); - void SetUserSynonyms(const vector& synonyms); - bool UserSynonymsAvailable() const; - bool GetUseAlternative() const; - void SetUseAlternative(bool use_alternative); - - // A database item may not be in user's list. - void AddtoUserList(); - bool IsInList() const; - void RemoveFromUserList(); - - // Is this item old enough to be updated? See last_modified for more - // information. - bool IsOldEnough() const; - - // After a successful update, an event item is removed from the queue and the - // relevant anime item is edited. - bool Edit(EventItem& item, const wstring& data, int status_code); - - // Following functions are called when a new episode is recognized. Actual - // time depends on user settings. - void StartWatching(Episode& episode); - void EndWatching(Episode episode); - bool IsUpdateAllowed(const Episode& episode, bool ignore_update_time); - void UpdateList(Episode& episode); - void AddToQueue(const Episode& episode, bool change_status); - - // MAL's API doesn't provide searching anime by ID, and some titles return - // no result due to special characters. Season data provide safe-to-search - // titles with keep_title flag on. - // TODO: Remove after searching by ID is made possible. - bool keep_title; - - // An item's series information will only be updated only if last_modified - // value is significantly older than the new one's. This helps us lower - // the number of requests we send to MAL. - time_t last_modified; - - private: - // Helper function - EventItem* SearchHistory(int search_mode) const; - - // Series information, stored in db\anime.xml - SeriesInformation series_info_; - - // User information, stored in user\\anime.xml - some items are not - // in user's list, thus this member is not valid for every item. - std::shared_ptr my_info_; - - // Local information, stored temporarily - LocalInformation local_info_; - - // Pointer to the parent database which holds this item - static Database* database_; -}; - -} // namespace anime - -#endif // ANIME_ITEM_H \ No newline at end of file diff --git a/anime_user.cpp b/anime_user.cpp deleted file mode 100644 index 23c5a1812..000000000 --- a/anime_user.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "anime_db.h" -#include "anime_user.h" - -#include "history.h" -#include "http.h" -#include "myanimelist.h" -#include "string.h" - -anime::Database* anime::ListUser::database_ = &AnimeDatabase; - -namespace anime { - -// ============================================================================= - -User::User() - : id(0), - watching(0), - completed(0), - on_hold(0), - dropped(0), - plan_to_watch(0) { -} - -ListUser::ListUser() { -} - -void ListUser::Clear() { - user_.id = 0; - user_.watching = 0; - user_.completed = 0; - user_.on_hold = 0; - user_.dropped = 0; - user_.plan_to_watch = 0; - user_.name.clear(); - user_.days_spent_watching.clear(); -} - -int ListUser::GetId() const { - return user_.id; -} - -int ListUser::GetItemCount(int status, bool check_events) const { - int count = 0; - - // Get current count - switch (status) { - case mal::MYSTATUS_WATCHING: - count = user_.watching; - break; - case mal::MYSTATUS_COMPLETED: - count = user_.completed; - break; - case mal::MYSTATUS_ONHOLD: - count = user_.on_hold; - break; - case mal::MYSTATUS_DROPPED: - count = user_.dropped; - break; - case mal::MYSTATUS_PLANTOWATCH: - count = user_.plan_to_watch; - break; - } - - // Search event queue for status changes - if (check_events) { - for (auto it = History.queue.items.begin(); it != History.queue.items.end(); ++it) { - if (it->mode == HTTP_MAL_AnimeAdd) continue; - if (it->status) { - if (status == *it->status) { - count++; - } else { - auto anime_item = database_->FindItem(it->anime_id); - if (anime_item && status == anime_item->GetMyStatus(false)) { - count--; - } - } - } - } - } - - return count; -} - -const wstring& ListUser::GetName() const { - return user_.name; -} - -void ListUser::DecreaseItemCount(int status, bool save_list) { - int count = GetItemCount(status, false) - 1; - SetItemCount(status, count, save_list); -} - -void ListUser::IncreaseItemCount(int status, bool save_list) { - int count = GetItemCount(status, false) + 1; - SetItemCount(status, count, save_list); -} - -void ListUser::SetItemCount(int status, int count, bool save_list) { - switch (status) { - case mal::MYSTATUS_WATCHING: - user_.watching = count; - if (save_list) database_->SaveList( - -1, L"user_watching", ToWstr(user_.watching), EDIT_USER); - break; - case mal::MYSTATUS_COMPLETED: - user_.completed = count; - if (save_list) database_->SaveList( - -1, L"user_completed", ToWstr(user_.completed), EDIT_USER); - break; - case mal::MYSTATUS_ONHOLD: - user_.on_hold = count; - if (save_list) database_->SaveList( - -1, L"user_onhold", ToWstr(user_.on_hold), EDIT_USER); - break; - case mal::MYSTATUS_DROPPED: - user_.dropped = count; - if (save_list) database_->SaveList( - -1, L"user_dropped", ToWstr(user_.dropped), EDIT_USER); - break; - case mal::MYSTATUS_PLANTOWATCH: - user_.plan_to_watch = count; - if (save_list) database_->SaveList( - -1, L"user_plantowatch", ToWstr(user_.plan_to_watch), EDIT_USER); - break; - } -} - -void ListUser::SetId(int id) { - user_.id = id; -} - -void ListUser::SetName(const wstring& name) { - user_.name = name; -} - -void ListUser::SetDaysSpentWatching(const wstring& days_spent_watching) { - user_.days_spent_watching = days_spent_watching; -} - -} // namespace anime \ No newline at end of file diff --git a/anime_user.h b/anime_user.h deleted file mode 100644 index baa97c5f0..000000000 --- a/anime_user.h +++ /dev/null @@ -1,69 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef ANIME_LIST_USER_H -#define ANIME_LIST_USER_H - -#include "std.h" - -namespace anime { - -class Database; - -class User { - public: - User(); - virtual ~User() {} - - int id; - int watching; - int completed; - int on_hold; - int dropped; - int plan_to_watch; - wstring name; - wstring days_spent_watching; -}; - -class ListUser { - public: - ListUser(); - virtual ~ListUser() {} - - void Clear(); - - int GetId() const; - int GetItemCount(int status, bool check_events = true) const; - const wstring& GetName() const; - - void DecreaseItemCount(int status, bool save_list = true); - void IncreaseItemCount(int status, bool save_list = true); - - void SetId(int id); - void SetItemCount(int status, int count, bool save_list = true); - void SetName(const wstring& name); - void SetDaysSpentWatching(const wstring& days_spent_watching); - - private: - User user_; - static Database* database_; -}; - -} // namespace anime - -#endif // ANIME_LIST_USER_H \ No newline at end of file diff --git a/announce.cpp b/announce.cpp deleted file mode 100644 index 21adf309b..000000000 --- a/announce.cpp +++ /dev/null @@ -1,490 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "announce.h" - -#include "anime.h" -#include "anime_episode.h" -#include "common.h" -#include "dde.h" -#include "http.h" -#include "logger.h" -#include "settings.h" -#include "string.h" -#include "taiga.h" - -#include "dlg/dlg_main.h" - -#include "win32/win_taskdialog.h" - -class Announcer Announcer; -class Skype Skype; -class Twitter Twitter; - -// ============================================================================= - -void Announcer::Clear(int modes, bool force) { - if (modes & ANNOUNCE_TO_HTTP) { - if (Settings.Announce.HTTP.enabled || force) { - ToHttp(Settings.Announce.HTTP.url, L""); - } - } - - if (modes & ANNOUNCE_TO_MESSENGER) { - if (Settings.Announce.MSN.enabled || force) { - ToMessenger(L"", L"", L"", false); - } - } - - if (modes & ANNOUNCE_TO_MIRC) { - // Not available - } - - if (modes & ANNOUNCE_TO_SKYPE) { - if (Settings.Announce.Skype.enabled || force) { - ToSkype(Skype.previous_mood); - } - } - - if (modes & ANNOUNCE_TO_TWITTER) { - // Not available - } -} - -void Announcer::Do(int modes, anime::Episode* episode, bool force) { - if (!force && !Settings.Program.General.enable_sharing) - return; - - if (!episode) - episode = &CurrentEpisode; - - if (modes & ANNOUNCE_TO_HTTP) { - if (Settings.Announce.HTTP.enabled || force) { - LOG(LevelDebug, L"HTTP"); - ToHttp(Settings.Announce.HTTP.url, - ReplaceVariables(Settings.Announce.HTTP.format, *episode, true, force)); - } - } - - if (episode->anime_id <= anime::ID_UNKNOWN) - return; - - if (modes & ANNOUNCE_TO_MESSENGER) { - if (Settings.Announce.MSN.enabled || force) { - LOG(LevelDebug, L"Messenger"); - ToMessenger(L"Taiga", L"MyAnimeList", - ReplaceVariables(Settings.Announce.MSN.format, *episode, false, force), true); - } - } - - if (modes & ANNOUNCE_TO_MIRC) { - if (Settings.Announce.MIRC.enabled || force) { - LOG(LevelDebug, L"mIRC"); - ToMirc(Settings.Announce.MIRC.service, - Settings.Announce.MIRC.channels, - ReplaceVariables(Settings.Announce.MIRC.format, *episode, false, force), - Settings.Announce.MIRC.mode, - Settings.Announce.MIRC.use_action, - Settings.Announce.MIRC.multi_server); - } - } - - if (modes & ANNOUNCE_TO_SKYPE) { - if (Settings.Announce.Skype.enabled || force) { - LOG(LevelDebug, L"Skype"); - ToSkype(ReplaceVariables(Settings.Announce.Skype.format, *episode, false, force)); - } - } - - if (modes & ANNOUNCE_TO_TWITTER) { - if (Settings.Announce.Twitter.enabled || force) { - LOG(LevelDebug, L"Twitter"); - ToTwitter(ReplaceVariables(Settings.Announce.Twitter.format, *episode, false, force)); - } - } -} - -// ============================================================================= - -/* HTTP */ - -void Announcer::ToHttp(wstring address, wstring data) { - if (address.empty() || data.empty()) return; - - Clients.sharing.http.Post(win32::Url(address), data, L"", HTTP_Silent); -} - -// ============================================================================= - -/* Messenger */ - -void Announcer::ToMessenger(wstring artist, wstring album, wstring title, BOOL show) { - if (title.empty() && show) return; - - COPYDATASTRUCT cds; - WCHAR buffer[256]; - - wstring wstr = L"\\0Music\\0" + ToWstr(show) + L"\\0{1}\\0" + - artist + L"\\0" + title + L"\\0" + album + L"\\0\\0"; - wcscpy_s(buffer, 256, wstr.c_str()); - - cds.dwData = 0x547; - cds.lpData = &buffer; - cds.cbData = (lstrlenW(buffer) * 2) + 2; - - HWND hMessenger = NULL; - while (hMessenger = FindWindowEx(NULL, hMessenger, L"MsnMsgrUIManager", NULL)) { - if (hMessenger > 0) { - SendMessage(hMessenger, WM_COPYDATA, NULL, (LPARAM)&cds); - } - } -} - -// ============================================================================= - -/* mIRC */ - -bool Announcer::ToMirc(wstring service, wstring channels, wstring data, int mode, BOOL use_action, BOOL multi_server) { - if (!FindWindow(L"mIRC", NULL)) return FALSE; - if (service.empty() || channels.empty() || data.empty()) return FALSE; - - // Initialize - DynamicDataExchange DDE; - if (!DDE.Initialize(/*APPCLASS_STANDARD | APPCMD_CLIENTONLY, TRUE*/)) { - win32::TaskDialog dlg(L"Announce to mIRC", TD_ICON_ERROR); - dlg.SetMainInstruction(L"DDE initialization failed."); - dlg.AddButton(L"OK", IDOK); - dlg.Show(g_hMain); - return false; - } - - // List channels - if (mode != MIRC_CHANNELMODE_CUSTOM) { - if (DDE.Connect(service, L"CHANNELS")) { - DDE.ClientTransaction(L" ", L"", &channels, XTYP_REQUEST); - DDE.Disconnect(); - } - } - vector channel_list; - Tokenize(channels, L" ,;", channel_list); - for (size_t i = 0; i < channel_list.size(); i++) { - Trim(channel_list[i]); - if (channel_list[i].empty()) { - continue; - } - if (channel_list[i].at(0) == '*') { - channel_list[i] = channel_list[i].substr(1); - if (mode == MIRC_CHANNELMODE_ACTIVE) { - wstring temp = channel_list[i]; - channel_list.clear(); - channel_list.push_back(temp); - break; - } - } - if (channel_list[i].at(0) != '#') { - channel_list[i].insert(channel_list[i].begin(), '#'); - } - } - - // Connect - if (!DDE.Connect(service, L"COMMAND")) { - win32::TaskDialog dlg(L"Announce to mIRC", TD_ICON_ERROR); - dlg.SetMainInstruction(L"DDE connection failed."); - dlg.SetContent(L"Please enable DDE server from mIRC Options > Other > DDE."); - dlg.AddButton(L"OK", IDOK); - dlg.Show(g_hMain); - DDE.UnInitialize(); - return false; - } - - // Send message to channels - for (size_t i = 0; i < channel_list.size(); i++) { - wstring message; - message += multi_server ? L"/scon -a " : L""; - message += use_action ? L"/describe " : L"/msg "; - message += channel_list[i] + L" " + data; - DDE.ClientTransaction(L" ", message, NULL, XTYP_POKE); - } - - // Clean up - DDE.Disconnect(); - DDE.UnInitialize(); - return true; -} - -bool Announcer::TestMircConnection(wstring service) { - wstring content; - win32::TaskDialog dlg(L"Test DDE connection", TD_ICON_ERROR); - dlg.AddButton(L"OK", IDOK); - - // Search for mIRC window - if (!FindWindow(L"mIRC", NULL)) { - dlg.SetMainInstruction(L"mIRC is not running."); - dlg.Show(g_hMain); - return false; - } - - // Initialize - DynamicDataExchange DDE; - if (!DDE.Initialize(/*APPCLASS_STANDARD | APPCMD_CLIENTONLY, TRUE*/)) { - dlg.SetMainInstruction(L"DDE initialization failed."); - dlg.Show(g_hMain); - return false; - } - - // Try to connect - if (!DDE.Connect(service, L"CHANNELS")) { - dlg.SetMainInstruction(L"DDE connection failed."); - dlg.SetContent(L"Please enable DDE server from mIRC Options > Other > DDE."); - dlg.Show(g_hMain); - DDE.UnInitialize(); - return false; - } else { - wstring channels; - DDE.ClientTransaction(L" ", L"", &channels, XTYP_REQUEST); - if (!channels.empty()) content = L"Current channels: " + channels; - } - - // Success - dlg.SetMainIcon(TD_ICON_INFORMATION); - dlg.SetMainInstruction(L"Successfuly connected to DDE server!"); - dlg.SetContent(content.c_str()); - dlg.Show(g_hMain); - DDE.Disconnect(); - DDE.UnInitialize(); - return true; -} - -// ============================================================================= - -/* Skype */ - -const UINT Skype::wm_attach = ::RegisterWindowMessage(L"SkypeControlAPIAttach"); -const UINT Skype::wm_discover = ::RegisterWindowMessage(L"SkypeControlAPIDiscover"); - -Skype::Skype() - : hwnd(nullptr), - hwnd_skype(nullptr) { -} - -Skype::~Skype() { - window_.Destroy(); -} - -void Skype::Create() { - hwnd = window_.Create(); -} - -BOOL Skype::Discover() { - PDWORD_PTR sendMessageResult = nullptr; - return SendMessageTimeout(HWND_BROADCAST, wm_discover, - reinterpret_cast(hwnd), - 0, SMTO_NORMAL, 1000, sendMessageResult); -} - -BOOL Skype::SendCommand(const wstring& command) { - const char* buffer = ToANSI(command); - - COPYDATASTRUCT cds; - cds.dwData = 0; - cds.lpData = (void*)buffer; - cds.cbData = strlen(buffer) + 1; - - if (SendMessage(hwnd_skype, WM_COPYDATA, - reinterpret_cast(hwnd), - reinterpret_cast(&cds)) == FALSE) { - LOG(LevelError, L"WM_COPYDATA failed."); - hwnd_skype = nullptr; - return FALSE; - } else { - LOG(LevelDebug, L"WM_COPYDATA succeeded."); - return TRUE; - } -} - -BOOL Skype::GetMoodText() { - wstring command = L"GET PROFILE RICH_MOOD_TEXT"; - return SendCommand(command); -} - -BOOL Skype::SetMoodText(const wstring& mood) { - current_mood = mood; - wstring command = L"SET PROFILE RICH_MOOD_TEXT " + mood; - return SendCommand(command); -} - -void Skype::Window::PreRegisterClass(WNDCLASSEX& wc) { - wc.lpszClassName = L"TaigaSkypeW"; -} - -void Skype::Window::PreCreate(CREATESTRUCT& cs) { - cs.lpszName = L"Taiga <3 Skype"; - cs.style = WS_OVERLAPPEDWINDOW; -} - -LRESULT Skype::Window::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - if (::Skype.HandleMessage(uMsg, wParam, lParam)) - return TRUE; - - return WindowProcDefault(hwnd, uMsg, wParam, lParam); -} - -LRESULT Skype::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { - if (uMsg == WM_COPYDATA) { - if (hwnd_skype == nullptr || - hwnd_skype != reinterpret_cast(wParam)) - return FALSE; - - auto pCDS = reinterpret_cast(lParam); - wstring command = ToUTF8(reinterpret_cast(pCDS->lpData)); - LOG(LevelDebug, L"Received WM_COPYDATA: " + command); - - wstring profile_command = L"PROFILE RICH_MOOD_TEXT "; - if (StartsWith(command, profile_command)) { - wstring mood = command.substr(profile_command.length()); - if (mood != current_mood && mood != previous_mood) { - LOG(LevelDebug, L"Saved previous mood message: " + mood); - previous_mood = mood; - } - } - - return TRUE; - - } else if (uMsg == wm_attach) { - hwnd_skype = nullptr; - - switch (lParam) { - case SKYPECONTROLAPI_ATTACH_SUCCESS: - LOG(LevelDebug, L"Attach succeeded."); - hwnd_skype = reinterpret_cast(wParam); - GetMoodText(); - if (!current_mood.empty()) - SetMoodText(current_mood); - break; - case SKYPECONTROLAPI_ATTACH_PENDING_AUTHORIZATION: - LOG(LevelDebug, L"Waiting for user confirmation..."); - break; - case SKYPECONTROLAPI_ATTACH_REFUSED: - LOG(LevelError, L"User denied access to client."); - break; - case SKYPECONTROLAPI_ATTACH_NOT_AVAILABLE: - LOG(LevelError, L"API is not available."); - break; - case SKYPECONTROLAPI_ATTACH_API_AVAILABLE: - LOG(LevelDebug, L"API is now available."); - Discover(); - break; - default: - LOG(LevelDebug, L"Received unknown message."); - break; - } - - return TRUE; - - } else if (uMsg == wm_discover) { - LOG(LevelDebug, L"Received SkypeControlAPIDiscover message."); - } - - return FALSE; -} - -void Announcer::ToSkype(const wstring& mood) { - Skype.current_mood = mood; - - if (Skype.hwnd_skype == nullptr) { - Skype.Discover(); - } else { - Skype.SetMoodText(mood); - } -} - -// ============================================================================= - -/* Twitter */ - -Twitter::Twitter() { - // These are unique values that identify Taiga - oauth.ConsumerKey = L"9GZsCbqzjOrsPWlIlysvg"; - oauth.ConsumerSecret = L"ebjXyymbuLtjDvoxle9Ldj8YYIMoleORapIOoqBrjRw"; -} - -bool Twitter::RequestToken() { - wstring header = - Clients.sharing.twitter.GetDefaultHeader() + - oauth.BuildHeader( - L"https://api.twitter.com/oauth/request_token", - L"GET", NULL); - - Clients.sharing.twitter.SetHttpsEnabled(TRUE); - - return Clients.sharing.twitter.Connect( - L"api.twitter.com", L"oauth/request_token", - L"", L"GET", header, L"myanimelist.net", L"", - HTTP_Twitter_Request); -} - -bool Twitter::AccessToken(const wstring& key, const wstring& secret, const wstring& pin) { - wstring header = - Clients.sharing.twitter.GetDefaultHeader() + - oauth.BuildHeader( - L"https://api.twitter.com/oauth/access_token", - L"POST", NULL, - key, secret, pin); - - Clients.sharing.twitter.SetHttpsEnabled(TRUE); - - return Clients.sharing.twitter.Connect( - L"api.twitter.com", L"oauth/access_token", - L"", L"GET", header, L"myanimelist.net", L"", - HTTP_Twitter_Auth); -} - -bool Twitter::SetStatusText(const wstring& status_text) { - if (Settings.Announce.Twitter.oauth_key.empty() || Settings.Announce.Twitter.oauth_secret.empty()) { - return false; - } - if (status_text.empty() || status_text == status_text_) { - return false; - } - status_text_ = status_text; - - OAuthParameters post_parameters; - post_parameters[L"status"] = EncodeUrl(status_text_); - - wstring header = - Clients.sharing.twitter.GetDefaultHeader() + - oauth.BuildHeader( - L"https://api.twitter.com/1.1/statuses/update.json", - L"POST", &post_parameters, - Settings.Announce.Twitter.oauth_key, - Settings.Announce.Twitter.oauth_secret); - - Clients.sharing.twitter.SetHttpsEnabled(TRUE); - - return Clients.sharing.twitter.Connect( - L"api.twitter.com", L"1.1/statuses/update.json", - L"status=" + post_parameters[L"status"], - L"POST", header, L"myanimelist.net", L"", - HTTP_Twitter_Post); -} - -void Announcer::ToTwitter(const wstring& status_text) { - Twitter.SetStatusText(status_text); -} \ No newline at end of file diff --git a/announce.h b/announce.h deleted file mode 100644 index 20098d266..000000000 --- a/announce.h +++ /dev/null @@ -1,140 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef ANNOUNCE_H -#define ANNOUNCE_H - -#include "std.h" - -#include "third_party/oauth/oauth.h" - -#include "win32/win_window.h" - -namespace anime { -class Episode; -} - -// ============================================================================= - -enum AnnouncerModes { - ANNOUNCE_TO_HTTP = 0x01, - ANNOUNCE_TO_MESSENGER = 0x02, - ANNOUNCE_TO_MIRC = 0x04, - ANNOUNCE_TO_SKYPE = 0x08, - ANNOUNCE_TO_TWITTER = 0x10 -}; - -class Announcer { -public: - Announcer() {} - virtual ~Announcer() {} - - void Clear(int modes, bool force = false); - void Do(int modes, anime::Episode* episode = nullptr, bool force = false); - -public: - bool TestMircConnection(wstring service); - -private: - void ToHttp(wstring address, wstring data); - void ToMessenger(wstring artist, wstring album, wstring title, BOOL show); - bool ToMirc(wstring service, wstring channels, wstring data, int mode, BOOL use_action, BOOL multi_server); - void ToSkype(const wstring& mood); - void ToTwitter(const wstring& status_text); -}; - -extern Announcer Announcer; - -// ============================================================================= - -/* mIRC */ - -enum MircChannelMode { - MIRC_CHANNELMODE_ACTIVE = 1, - MIRC_CHANNELMODE_ALL = 2, - MIRC_CHANNELMODE_CUSTOM = 3 -}; - -// ============================================================================= - -/* Skype */ - -enum SkypeConnectionStatus { - SKYPECONTROLAPI_ATTACH_SUCCESS = 0, - SKYPECONTROLAPI_ATTACH_PENDING_AUTHORIZATION, - SKYPECONTROLAPI_ATTACH_REFUSED, - SKYPECONTROLAPI_ATTACH_NOT_AVAILABLE, - SKYPECONTROLAPI_ATTACH_API_AVAILABLE = 0x8001 -}; - -class Skype { -public: - Skype(); - virtual ~Skype(); - - void Create(); - BOOL Discover(); - LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam); - BOOL SendCommand(const wstring& command); - BOOL GetMoodText(); - BOOL SetMoodText(const wstring& mood); - - static const UINT wm_attach; - static const UINT wm_discover; - -public: - HWND hwnd, hwnd_skype; - wstring current_mood, previous_mood; - -private: - class Window : public win32::Window { - public: - Window() {} - virtual ~Window() {} - private: - void PreRegisterClass(WNDCLASSEX& wc); - void PreCreate(CREATESTRUCT& cs); - LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - } window_; -}; - -extern Skype Skype; - -// ============================================================================= - -/* Twitter */ - -class Twitter { -public: - Twitter(); - virtual ~Twitter() {} - - bool RequestToken(); - bool AccessToken(const wstring& key, const wstring& secret, const wstring& pin); - bool SetStatusText(const wstring& status_text); - -public: - COAuth oauth; - -private: - wstring status_text_; -}; - -extern Twitter Twitter; - -#endif // ANNOUNCE_H \ No newline at end of file diff --git a/common.cpp b/common.cpp deleted file mode 100644 index e58552a5a..000000000 --- a/common.cpp +++ /dev/null @@ -1,561 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "common.h" - -#include "anime_db.h" -#include "myanimelist.h" -#include "settings.h" -#include "string.h" -#include "theme.h" - -#include "third_party/base64/base64.h" -#include "third_party/zlib/zlib.h" - -#include "win32/win_registry.h" - -#define MAKEQWORD(a, b) ((QWORD)( ((QWORD) ((DWORD) (a))) << 32 | ((DWORD) (b)))) - -// ============================================================================= - -wstring Base64Decode(const wstring& str, bool for_filename) { - if (str.empty()) return L""; - Base64Coder coder; - string buff = ToANSI(str); - coder.Decode((BYTE*)buff.c_str(), buff.length()); - if (for_filename) { - wstring msg = ToUTF8(coder.DecodedMessage()); - ReplaceChar(msg, '-', '/'); - return msg; - } else { - return ToUTF8(coder.DecodedMessage()); - } -} - -wstring Base64Encode(const wstring& str, bool for_filename) { - if (str.empty()) return L""; - Base64Coder coder; - string buff = ToANSI(str); - coder.Encode((BYTE*)buff.c_str(), buff.length()); - if (for_filename) { - wstring msg = ToUTF8(coder.EncodedMessage()); - ReplaceChar(msg, '/', '-'); - return msg; - } else { - return ToUTF8(coder.EncodedMessage()); - } -} - -// ============================================================================= - -wstring CalculateCRC(const wstring& file) { - BYTE buffer[0x10000]; - DWORD dwBytesRead = 0; - - HANDLE hFile = CreateFile(file.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0); - if (hFile == INVALID_HANDLE_VALUE) return L""; - - ULONG crc = crc32(0L, Z_NULL, 0); - BOOL bSuccess = ReadFile(hFile, buffer, sizeof(buffer), &dwBytesRead, NULL); - while (bSuccess && dwBytesRead) { - crc = crc32(crc, buffer, dwBytesRead); - bSuccess = ReadFile(hFile, buffer, sizeof(buffer), &dwBytesRead, NULL); - } - - if (hFile != NULL) CloseHandle(hFile); - - wchar_t val[16] = {0}; - _ultow_s(crc, val, 16, 16); - wstring value = val; - if (value.length() < 8) { - value.insert(0, 8 - value.length(), '0'); - } - return value; -} - -// ============================================================================= - -bool IsEpisodeRange(const wstring& episode_number) { - return GetEpisodeLow(episode_number) != GetEpisodeHigh(episode_number); -} - -int GetEpisodeHigh(const wstring& episode_number) { - int value = 1, pos = InStrRev(episode_number, L"-", episode_number.length()); - if (pos == episode_number.length() - 1) { - value = ToInt(episode_number.substr(0, pos)); - } else if (pos > -1) { - value = ToInt(episode_number.substr(pos + 1)); - } else { - value = ToInt(episode_number); - } - return value; -} - -int GetEpisodeLow(const wstring& episode_number) { - return ToInt(episode_number); // ToInt() stops at - -} - -void SplitEpisodeNumbers(const wstring& input, vector& output) { - if (input.empty()) return; - vector numbers; - Split(input, L"-", numbers); - for (auto it = numbers.begin(); it != numbers.end(); ++it) { - output.push_back(ToInt(*it)); - } -} - -wstring JoinEpisodeNumbers(const vector& input) { - wstring output; - for (auto it = input.begin(); it != input.end(); ++it) { - if (!output.empty()) output += L"-"; - output += ToWstr(*it); - } - return output; -} - -int TranslateResolution(const wstring& str, bool return_validity) { - // *###x###* - if (str.length() > 6) { - int pos = InStr(str, L"x", 0); - if (pos > -1) { - for (unsigned int i = 0; i < str.length(); i++) { - if (i != pos && !IsNumeric(str.at(i))) return 0; - } - return return_validity ? - TRUE : ToInt(str.substr(pos + 1)); - } - - // *###p - } else if (str.length() > 3) { - if (str.at(str.length() - 1) == 'p') { - for (unsigned int i = 0; i < str.length() - 1; i++) { - if (!IsNumeric(str.at(i))) return 0; - } - return return_validity ? - TRUE : ToInt(str.substr(0, str.length() - 1)); - } - } - - return 0; -} - -// ============================================================================= - -int StatusToIcon(int status) { - switch (status) { - case mal::STATUS_AIRING: - return ICON16_GREEN; - case mal::STATUS_FINISHED: - return ICON16_BLUE; - case mal::STATUS_NOTYETAIRED: - return ICON16_RED; - default: - return ICON16_GRAY; - } -} - -// ============================================================================= - -wstring FormatError(DWORD dwError, LPCWSTR lpSource) { - DWORD dwFlags = FORMAT_MESSAGE_IGNORE_INSERTS; - HMODULE hInstance = NULL; - const DWORD size = 101; - WCHAR buffer[size]; - - if (lpSource) { - dwFlags |= FORMAT_MESSAGE_FROM_HMODULE; - hInstance = LoadLibrary(lpSource); - if (!hInstance) return L""; - } else { - dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM; - } - - if (FormatMessage(dwFlags, hInstance, dwError, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buffer, size, NULL)) { - if (hInstance) FreeLibrary(hInstance); - return buffer; - } else { - if (hInstance) FreeLibrary(hInstance); - return ToWstr(dwError); - } -} - -void SetSharedCursor(LPCWSTR name) { - SetCursor(reinterpret_cast(LoadImage(nullptr, name, IMAGE_CURSOR, - 0, 0, LR_SHARED))); -} - -// ============================================================================= - -unsigned long GetFileAge(const wstring& path) { - FILETIME ft_file, ft_now; - - // Get the time the file was last modified - HANDLE hFile = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile == INVALID_HANDLE_VALUE) return 0; - BOOL result = GetFileTime(hFile, NULL, NULL, &ft_file); - CloseHandle(hFile); - if (!result) return 0; - - // Get current time - SYSTEMTIME st_now; - GetSystemTime(&st_now); - SystemTimeToFileTime(&st_now, &ft_now); - - // Convert to ULARGE_INTEGER - ULARGE_INTEGER ul_file, ul_now; - ul_file.LowPart = ft_file.dwLowDateTime; - ul_file.HighPart = ft_file.dwHighDateTime; - ul_now.LowPart = ft_now.dwLowDateTime; - ul_now.HighPart = ft_now.dwHighDateTime; - - // Return difference in seconds - return static_cast((ul_now.QuadPart - ul_file.QuadPart) / 10000000); -} - -QWORD GetFileSize(const wstring& path) { - QWORD file_size = 0; - HANDLE hFile = CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, - NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - - if (hFile != INVALID_HANDLE_VALUE) { - DWORD size_low = 0, size_high = 0; - size_low = ::GetFileSize(hFile, &size_high); - file_size = (size_low == INVALID_FILE_SIZE) ? 0 : MAKEQWORD(size_high, size_low); - CloseHandle(hFile); - } - - return file_size; -} - -QWORD GetFolderSize(const wstring& path, bool recursive) { - QWORD folder_size = 0; - WIN32_FIND_DATA wfd; - wstring folder = path + L"*.*"; - - HANDLE hFind = FindFirstFile(folder.c_str(), &wfd); - if (hFind == INVALID_HANDLE_VALUE) return 0; - - do { - if (recursive && wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - if (wcscmp(wfd.cFileName, L".") != 0 && wcscmp(wfd.cFileName, L"..") != 0) { - folder = path + wfd.cFileName + L"\\"; - folder_size += GetFolderSize(folder, recursive); - } - } - folder_size += (wfd.nFileSizeHigh * (MAXDWORD + 1)) + wfd.nFileSizeLow; - } while (FindNextFile(hFind, &wfd)); - - FindClose(hFind); - return folder_size; -} - -// ============================================================================= - -bool Execute(const wstring& path, const wstring& parameters) { - if (path.empty()) return false; - HINSTANCE value = ShellExecute(NULL, L"open", path.c_str(), parameters.c_str(), NULL, SW_SHOWNORMAL); - return reinterpret_cast(value) > 32; -} - -BOOL ExecuteEx(const wstring& path, const wstring& parameters) { - SHELLEXECUTEINFO si = {0}; - si.cbSize = sizeof(SHELLEXECUTEINFO); - si.fMask = SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI | SEE_MASK_UNICODE; - si.lpVerb = L"open"; - si.lpFile = path.c_str(); - si.lpParameters = parameters.c_str(); - si.nShow = SW_SHOWNORMAL; - return ShellExecuteEx(&si); -} - -void ExecuteLink(const wstring& link) { - ShellExecute(NULL, NULL, link.c_str(), NULL, NULL, SW_SHOWNORMAL); -} - -wstring ExpandEnvironmentStrings(const wstring& path) { - WCHAR buff[MAX_PATH]; - if (::ExpandEnvironmentStrings(path.c_str(), buff, MAX_PATH)) { - return buff; - } else { - return path; - } -} - -wstring BrowseForFile(HWND hwndOwner, LPCWSTR lpstrTitle, LPCWSTR lpstrFilter) { - WCHAR szFile[MAX_PATH] = {'\0'}; - - if (!lpstrFilter) { - lpstrFilter = L"All files (*.*)\0*.*\0"; - } - - OPENFILENAME ofn = {0}; - ofn.lStructSize = sizeof(OPENFILENAME); - ofn.hwndOwner = hwndOwner; - ofn.lpstrFile = szFile; - ofn.lpstrFilter = lpstrFilter; - ofn.lpstrTitle = lpstrTitle; - ofn.nMaxFile = sizeof(szFile); - ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST; - - if (GetOpenFileName(&ofn)) { - return szFile; - } else { - return L""; - } -} - -bool BrowseForFolderVista(HWND hwnd, const wstring& title, const wstring& default_folder, wstring& output) { - IFileDialog* pFileDialog; - bool result = false; - - HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, - nullptr, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&pFileDialog)); - - if (SUCCEEDED(hr)) { - FILEOPENDIALOGOPTIONS fos; - pFileDialog->GetOptions(&fos); - fos |= FOS_PICKFOLDERS; - pFileDialog->SetOptions(fos); - - if (!title.empty()) - pFileDialog->SetTitle(title.c_str()); - - if (!default_folder.empty()) { - IShellItem* pShellItem; - HRESULT hr = NULL; - - typedef HRESULT (WINAPI *_SHCreateItemFromParsingName)( - PCWSTR pszPath, IBindCtx *pbc, REFIID riid, void **ppv); - HMODULE hShell32 = LoadLibrary(L"shell32.dll"); - if (hShell32 != NULL) { - _SHCreateItemFromParsingName proc = - (_SHCreateItemFromParsingName)GetProcAddress(hShell32, "SHCreateItemFromParsingName"); - if (proc != NULL) { - hr = (proc)(default_folder.c_str(), nullptr, IID_IShellItem, - reinterpret_cast(&pShellItem)); - } - FreeLibrary(hShell32); - } - - if (SUCCEEDED(hr)) { - pFileDialog->SetDefaultFolder(pShellItem); - pShellItem->Release(); - } - } - - hr = pFileDialog->Show(hwnd); - if (SUCCEEDED(hr)) { - IShellItem* pShellItem; - hr = pFileDialog->GetFolder(&pShellItem); - if (SUCCEEDED(hr)) { - LPWSTR path = nullptr; - hr = pShellItem->GetDisplayName(SIGDN_FILESYSPATH, &path); - if (SUCCEEDED(hr)) { - output.assign(path); - CoTaskMemFree(path); - result = true; - } - pShellItem->Release(); - } - } - - pFileDialog->Release(); - } - - return result; -} - -static int CALLBACK BrowseForFolderXPProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) { - switch (uMsg) { - case BFFM_INITIALIZED: - if (lpData != 0) - SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData); - break; - } - return 0; -} - -bool BrowseForFolderXP(HWND hwnd, const wstring& title, const wstring& default_path, wstring& output) { - BROWSEINFO bi = {0}; - bi.hwndOwner = hwnd; - bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_NONEWFOLDERBUTTON; - - if (!title.empty()) - bi.lpszTitle = title.c_str(); - - if (!default_path.empty()) { - WCHAR lpszDefault[MAX_PATH] = {'\0'}; - default_path.copy(lpszDefault, MAX_PATH); - bi.lParam = reinterpret_cast(lpszDefault); - bi.lpfn = BrowseForFolderXPProc; - } - - PIDLIST_ABSOLUTE pidl = SHBrowseForFolder(&bi); - if (pidl == nullptr) - return false; - - WCHAR path[MAX_PATH]; - SHGetPathFromIDList(pidl, path); - output = path; - if (output.empty()) - return false; - - return true; -} - -BOOL BrowseForFolder(HWND hwnd, const wstring& title, const wstring& default_path, wstring& output) { - if (win32::GetWinVersion() >= win32::VERSION_VISTA) { - return BrowseForFolderVista(hwnd, title, default_path, output); - } else { - return BrowseForFolderXP(hwnd, title, default_path, output); - } -} - -bool CreateFolder(const wstring& path) { - return SHCreateDirectoryEx(NULL, path.c_str(), NULL) == ERROR_SUCCESS; -} - -int DeleteFolder(wstring path) { - if (path.back() == '\\') path.pop_back(); - path.push_back('\0'); - SHFILEOPSTRUCT fos = {0}; - fos.wFunc = FO_DELETE; - fos.pFrom = path.c_str(); - fos.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT; - return SHFileOperation(&fos); -} - -bool FileExists(const wstring& file) { - if (file.empty()) return false; - HANDLE hFile = CreateFile(file.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile != INVALID_HANDLE_VALUE) { - CloseHandle(hFile); - return true; - } - return false; -} - -bool FolderExists(const wstring& path) { - DWORD file_attr = GetFileAttributes(path.c_str()); - return (file_attr != INVALID_FILE_ATTRIBUTES) && (file_attr & FILE_ATTRIBUTE_DIRECTORY); -} - -bool PathExists(const wstring& path) { - return GetFileAttributes(path.c_str()) != INVALID_FILE_ATTRIBUTES; -} - -void ValidateFileName(wstring& file) { - EraseChars(file, L"\\/:*?<>|"); -} - -wstring GetDefaultAppPath(const wstring& extension, const wstring& default_value) { - win32::Registry reg; - reg.OpenKey(HKEY_CLASSES_ROOT, extension, 0, KEY_QUERY_VALUE); - wstring path = reg.QueryValue(L""); - - if (!path.empty()) { - path += L"\\shell\\open\\command"; - reg.OpenKey(HKEY_CLASSES_ROOT, path, 0, KEY_QUERY_VALUE); - path = reg.QueryValue(L""); - Replace(path, L"\"", L""); - Trim(path, L" %1"); - } - - reg.CloseKey(); - return path.empty() ? default_value : path; -} - -int PopulateFiles(vector& file_list, wstring path, wstring extension, bool recursive, bool trim_extension) { - if (path.empty()) return 0; - wstring folder = path + L"*.*"; - int found = 0; - - WIN32_FIND_DATA wfd; - HANDLE hFind = FindFirstFile(folder.c_str(), &wfd); - if (hFind != INVALID_HANDLE_VALUE) { - do { - if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - if (recursive && wcscmp(wfd.cFileName, L".") != 0 && wcscmp(wfd.cFileName, L"..") != 0) { - folder = path + wfd.cFileName + L"\\"; - found += PopulateFiles(file_list, folder, extension, recursive, trim_extension); - } - } else if (wfd.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY) { - if (extension.empty() || IsEqual(GetFileExtension(wfd.cFileName), extension)) { - if (trim_extension) { - file_list.push_back(GetFileWithoutExtension(wfd.cFileName)); - } else { - file_list.push_back(wfd.cFileName); - } - found++; - } - } - } while (FindNextFile(hFind, &wfd)); - FindClose(hFind); - } - - return found; -} - -int PopulateFolders(vector& folder_list, wstring path) { - if (path.empty()) return 0; - path += L"*.*"; - int found = 0; - - WIN32_FIND_DATA wfd; - HANDLE hFind = FindFirstFile(path.c_str(), &wfd); - if (hFind != INVALID_HANDLE_VALUE) { - do { - if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - if (wcscmp(wfd.cFileName, L".") != 0 && wcscmp(wfd.cFileName, L"..") != 0) { - found++; - folder_list.push_back(wfd.cFileName); - } - } - } while (FindNextFile(hFind, &wfd)); - FindClose(hFind); - } - - return found; -} - -wstring ToSizeString(QWORD qwSize) { - wstring size, unit; - - if (qwSize > 1073741824) { // 2^30 - size = ToWstr(static_cast(qwSize) / 1073741824, 2); - unit = L" GB"; - } else if (qwSize > 1048576) { // 2^20 - size = ToWstr(static_cast(qwSize) / 1048576, 2); - unit = L" MB"; - } else if (qwSize > 1024) { // 2^10 - size = ToWstr(static_cast(qwSize) / 1024, 2); - unit = L" KB"; - } else { - size = ToWstr(qwSize); - unit = L" bytes"; - } - - return size + unit; -} \ No newline at end of file diff --git a/common.h b/common.h deleted file mode 100644 index 6766df065..000000000 --- a/common.h +++ /dev/null @@ -1,120 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef COMMON_H -#define COMMON_H - -#include "std.h" - -// ListView sort types -enum ListSortType { - LIST_SORTTYPE_DEFAULT, - LIST_SORTTYPE_EPISODES, - LIST_SORTTYPE_FILESIZE, - LIST_SORTTYPE_LASTUPDATED, - LIST_SORTTYPE_NUMBER, - LIST_SORTTYPE_POPULARITY, - LIST_SORTTYPE_PROGRESS, - LIST_SORTTYPE_SCORE, - LIST_SORTTYPE_STARTDATE, - LIST_SORTTYPE_TITLE -}; - -typedef unsigned __int64 QWORD, *LPQWORD; - -namespace anime { -class Episode; -class Item; -} - -// ============================================================================= - -// action.cpp -void ExecuteAction(wstring action, WPARAM wParam = 0, LPARAM lParam = 0); - -// common.cpp -wstring Base64Decode(const wstring& str, bool for_filename = false); -wstring Base64Encode(const wstring& str, bool for_filename = false); -wstring CalculateCRC(const wstring& file); -bool IsEpisodeRange(const wstring& episode_number); -int GetEpisodeHigh(const wstring& episode_number); -int GetEpisodeLow(const wstring& episode_number); -void SplitEpisodeNumbers(const wstring& input, vector& output); -wstring JoinEpisodeNumbers(const vector& input); -int TranslateResolution(const wstring& str, bool return_validity = false); -int StatusToIcon(int status); -wstring FormatError(DWORD dwError, LPCWSTR lpSource = NULL); -void SetSharedCursor(LPCWSTR name); -unsigned long GetFileAge(const wstring& path); -QWORD GetFileSize(const wstring& path); -QWORD GetFolderSize(const wstring& path, bool recursive); -bool Execute(const wstring& path, const wstring& parameters = L""); -BOOL ExecuteEx(const wstring& path, const wstring& parameters = L""); -void ExecuteLink(const wstring& link); -wstring ExpandEnvironmentStrings(const wstring& path); -wstring BrowseForFile(HWND hwndOwner, LPCWSTR lpstrTitle, LPCWSTR lpstrFilter = NULL); -BOOL BrowseForFolder(HWND hwnd, const wstring& title, const wstring& default_path, wstring& output); -bool CreateFolder(const wstring& path); -int DeleteFolder(wstring path); -bool FileExists(const wstring& file); -bool FolderExists(const wstring& folder); -bool PathExists(const wstring& path); -void ValidateFileName(wstring& path); -wstring GetDefaultAppPath(const wstring& extension, const wstring& default_value); -int PopulateFiles(vector& file_list, wstring path, wstring extension = L"", bool recursive = false, bool trim_extension = false); -int PopulateFolders(vector& folder_list, wstring path); -wstring ToSizeString(QWORD qwSize); - -// encryption.cpp -wstring SimpleEncrypt(wstring str); -wstring SimpleDecrypt(wstring str); - -// gzip.cpp -bool UncompressGzippedFile(const string& file, string& output); -bool UncompressGzippedString(const string& input, string& output); - -// list_sort.cpp -int CALLBACK ListViewCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort); - -// menu.cpp -void UpdateAllMenus(anime::Item* anime_item = nullptr); -void UpdateAnimeMenu(anime::Item* anime_item); -void UpdateAnnounceMenu(); -void UpdateExternalLinksMenu(); -void UpdateFoldersMenu(); -void UpdateSearchListMenu(bool enabled = false); -void UpdateSeasonListMenu(bool enabled = false); -void UpdateSeasonMenu(); -void UpdateToolsMenu(); -void UpdateTrayMenu(); -void UpdateViewMenu(); - -// script.cpp -wstring EvaluateFunction(const wstring& func_name, const wstring& func_body); -bool IsScriptFunction(const wstring& str); -bool IsScriptVariable(const wstring& str); -wstring ReplaceVariables(wstring str, const anime::Episode& episode, - bool url_encode = false, bool is_manual = false, bool is_preview = false); -wstring EscapeScriptEntities(const wstring& str); -wstring UnescapeScriptEntities(const wstring& str); - -// search.cpp -void ScanAvailableEpisodes(int anime_id, bool check_folder, bool silent); -wstring SearchFileFolder(anime::Item& anime_item, const wstring& root, int episode_number, bool search_folder); - -#endif // COMMON_H \ No newline at end of file diff --git a/data/db/season/2011_fall.xml b/data/db/season/2011_fall.xml index af0ef8e32..b6231d58f 100644 --- a/data/db/season/2011_fall.xml +++ b/data/db/season/2011_fall.xml @@ -1,637 +1,727 @@ - - - - Fall 2011 - 1380254460 - - - 3 - 10624 - Starchild Records, GoHands, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/11/29197.jpg - Mardock Scramble: The Second Combustion - - - 3 - 10731 - Production I.G, NAS, M.S.C - http://cdn.myanimelist.net/images/anime/3/32079.jpg - Prince of Tennis: Eikokushiki Teikyuu Shiro Kessen! - - - 2 - 10582 - Diomedea - http://cdn.myanimelist.net/images/anime/11/30853.jpg - Astarotte no Omocha! EX - - - 5 - 11103 - TNK, Kinema Citrus - http://cdn.myanimelist.net/images/anime/7/37953.jpg - Busou Shinki Moon Angel - - - 3 - 16756 - Vasoon Animation - http://cdn.myanimelist.net/images/anime/8/45068.jpg - Kuiba - - - 3 - 10389 - Production I.G, GKids - http://cdn.myanimelist.net/images/anime/2/43557.jpg - Momo e no Tegami - - - 3 - 10715 - Bones, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/11/33219.jpg - Towa no Quon 4: Guren no Shoushin - - - 6 - 19727 - - http://cdn.myanimelist.net/images/anime/5/52743.jpg - Nexus - - - 6 - 11583 - Manglobe - http://cdn.myanimelist.net/images/anime/3/32325.jpg - Natsuiro Surprise - - - 2 - 10805 - Geneon Universal Entertainment, Manglobe, Shogakukan Productions, Shogakukan Music & Digital Entertainment - http://cdn.myanimelist.net/images/anime/12/32297.jpg - Kami nomi zo Shiru Sekai: 4-nin to Idol - - - 3 - 10408 - Aniplex, Brains Base - http://cdn.myanimelist.net/images/anime/8/38229.jpg - Hotarubi no Mori e - - - 1 - 11017 - Sunrise - http://cdn.myanimelist.net/images/anime/3/32193.jpg - Battle Spirits: Heroes - - - 4 - 10832 - Satelight - http://cdn.myanimelist.net/images/anime/9/31995.jpg - Ikoku Meiro no Croisée: Yune & Alice - - - 4 - 10834 - Silver Link - http://cdn.myanimelist.net/images/anime/7/41417.jpg - Baka to Test to Shoukanjuu: Spinout! Sore ga Bokura no Nichijou - - - 4 - 13681 - AIC Plus+ - http://cdn.myanimelist.net/images/anime/12/37991.jpg - Nekogami Yaoyorozu Specials - - - 4 - 11715 - Brains Base, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/8/32717.jpg - Kamisama Dolls Specials - - - 4 - 11731 - - http://cdn.myanimelist.net/images/anime/9/32751.jpg - Chi-Sui Maru Specials - - - 6 - 11507 - Manglobe - http://cdn.myanimelist.net/images/anime/2/32091.jpg - Kami nomi zo Shiru Sekai: Nonstop!! Hunters - - - 2 - 10897 - AIC Build, Bushiroad Inc. - http://cdn.myanimelist.net/images/anime/8/30097.jpg - Boku wa Tomodachi ga Sukunai Episode 0 - - - 2 - 16377 - CoMix Wave - http://cdn.myanimelist.net/images/anime/6/44191.jpg - Peeping Life: The Perfect Extension - - - 4 - 11083 - Studio Deen - http://cdn.myanimelist.net/images/anime/12/31455.jpg - Nurarihyon no Mago: Sennen Makyou Recaps - - - 2 - 13993 - Studio Blanc - http://cdn.myanimelist.net/images/anime/10/38821.jpg - Goulart Knights: Evoked the Beginning White - - - 1 - 10378 - TV Tokyo, Diomedea, Nomad, Lantis, Pony Canyon, Production Reed, Encourage Films, TV Tokyo Music, Studio Jack - http://cdn.myanimelist.net/images/anime/6/30120.jpg - Shinryaku!? Ika Musume - - - 4 - 11505 - Satelight - http://cdn.myanimelist.net/images/anime/11/32089.jpg - Kiss Dum Special - - - 4 - 12579 - Zexcs - http://cdn.myanimelist.net/images/anime/5/35285.jpg - Itsuka Tenma no Kuro Usagi Picture Drama - - - 1 - 10521 - Aniplex, A-1 Pictures, Yomiuri Telecasting Corporation, Rakuonsha - http://cdn.myanimelist.net/images/anime/4/33739.jpg - Working'!! - - - 3 - 9000 - Madhouse Studios, TV Tokyo, TMS Entertainment, Movic, NIS America, Inc. - http://cdn.myanimelist.net/images/anime/3/36903.jpg - Toaru Hikuushi e no Tsuioku - - - 1 - 10030 - J.C. Staff, NHK, Media Blasters - http://cdn.myanimelist.net/images/anime/3/34923.jpg - Bakuman. 2 - - - 1 - 10578 - Starchild Records, FUNimation Entertainment, Silver Link - http://cdn.myanimelist.net/images/anime/13/32285.jpg - - - - 1 - 11061 - Madhouse Studios, VAP - http://cdn.myanimelist.net/images/anime/11/33657.jpg - Hunter x Hunter (2011) - - - 1 - 10087 - Aniplex, ufotable, Nitroplus, Aniplex of America, seikaisha, Notes - http://cdn.myanimelist.net/images/anime/8/29490.jpg - Fate/Zero - - - 1 - 11385 - Toei Animation - http://cdn.myanimelist.net/images/anime/8/31793.jpg - Digimon Xros Wars: Toki wo Kakeru Shounen Hunter-tachi - - - 1 - 11177 - SynergySP, TV Tokyo Music - http://cdn.myanimelist.net/images/anime/2/32803.jpg - Cross Fight B-Daman - - - 1 - 10456 - Sunrise, Lantis, Sentai Filmworks, ASCII Media Works - http://cdn.myanimelist.net/images/anime/4/33743.jpg - Kyoukaisenjou no Horizon - - - 1 - 10213 - Sentai Filmworks, Lerche - http://cdn.myanimelist.net/images/anime/4/32541.jpg - Maji de Watashi ni Koi Shinasai! - - - 1 - 10997 - - http://cdn.myanimelist.net/images/anime/4/30525.jpg - Fujilog 2nd Season - - - 1 - 9981 - Sunrise, NHK Enterprises, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/12/34045.jpg - Phi Brain: Kami no Puzzle - - - 1 - 10232 - TYO Animations, BIGLOBE - http://cdn.myanimelist.net/images/anime/13/45368.jpg - Tamayura: Hitotose - - - 1 - 11547 - Milky Cartoon, LMD, G-mode - http://cdn.myanimelist.net/images/anime/11/39031.jpg - Sengoku☆Paradise Kiwami - - - 4 - 10845 - Hoods Entertainment - http://cdn.myanimelist.net/images/anime/3/29932.jpg - Manyuu Hikenchou Specials - - - 4 - 12239 - Hoods Entertainment - http://cdn.myanimelist.net/images/anime/8/34477.jpg - Manyuu Hikenchou Picture Drama - - - 1 - 10460 - J.C. Staff, TV Tokyo, Aniplex, Square Enix, NAS, Movic, Kimi To Boku Production Partners - http://cdn.myanimelist.net/images/anime/4/34949.jpg - Kimi to Boku. - - - 1 - 10800 - Madhouse Studios, VAP - http://cdn.myanimelist.net/images/anime/3/35749.jpg - Chihayafuru - - - 1 - 11615 - DAX Production, Takeshobo, Seven - http://cdn.myanimelist.net/images/anime/7/32933.jpg - Morita-san wa Mukuchi. 2 - - - 5 - 12411 - Bones, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/11/34935.jpg - UN-GO: Inga Nikki - - - 1 - 10397 - Manglobe, Frontier Works, Lantis, Media Factory, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/9/39303.jpg - Mashiroiro Symphony: The Color of Lovers - - - 1 - 9936 - AIC, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/9/34955.jpg - Maken-Ki! - - - 1 - 10588 - Aniplex, AIC A.S.T.A., Sentai Filmworks, ASCII Media Works, Index - http://cdn.myanimelist.net/images/anime/4/29107.jpg - Persona 4 The Animation - - - 1 - 10719 - FUNimation Entertainment, Media Factory, TBS, AIC Build, Bushiroad Inc. - http://cdn.myanimelist.net/images/anime/8/32873.jpg - Boku wa Tomodachi ga Sukunai - - - 1 - 11123 - Studio Deen, The Right Stuf International - http://cdn.myanimelist.net/images/anime/8/34871.jpg - Sekaiichi Hatsukoi 2 - - - 1 - 6773 - J.C. Staff, Geneon Universal Entertainment, FUNimation Entertainment, Warner Bros., ASCII Media Works, Showgate - http://cdn.myanimelist.net/images/anime/9/32539.jpg - Shakugan no Shana III (Final) - - - 2 - 10418 - Manglobe, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/8/33033.jpg - Deadman Wonderland OVA - - - 1 - 10808 - Sunrise, Lantis, Mainichi Broadcasting, Sony Music Entertainment - http://cdn.myanimelist.net/images/anime/12/42535.jpg - Mobile Suit Gundam AGE - - - 1 - 10620 - FUNimation Entertainment, Lantis, Kadokawa Shoten, Asread, Rakuonsha, Kadokawa Pictures Japan, The Klock Worx, chara-ani.com, 12 Diary Holders, Dwango, Sakura Create - http://cdn.myanimelist.net/images/anime/13/33465.jpg - Mirai Nikki (TV) - - - 1 - 10396 - AT-X, David Production - http://cdn.myanimelist.net/images/anime/3/32315.jpg - Ben-To - - - 2 - 10260 - Kinema Citrus, Sentai Filmworks, Nihon Falcom, Bushiroad Inc., Showgate - http://cdn.myanimelist.net/images/anime/11/35775.jpg - Eiyuu Densetsu: Sora no Kiseki - - - 2 - 11355 - CoMix Wave, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/5/32057.jpg - Kono Danshi, Uchuujin to Tatakaemasu. - - - 1 - 11457 - Shogakukan Productions, SynergySP, Half H.P Studio - http://cdn.myanimelist.net/images/anime/6/35927.jpg - Chibi☆Devi! - - - 1 - 11809 - Strawberry Meets Pictures - http://cdn.myanimelist.net/images/anime/8/34963.jpg - gdgd Fairies - - - 1 - 10793 - Production I.G, Aniplex, FUNimation Entertainment, Movic, Fuji TV, Fuji Pacific Music Publishing - http://cdn.myanimelist.net/images/anime/8/33713.jpg - Guilty Crown - - - 1 - 10798 - Bones, Dentsu, Fuji TV, Toho Company, Sentai Filmworks, Sony Music Entertainment, Fuji Pacific Music Publishing - http://cdn.myanimelist.net/images/anime/12/33009.jpg - UN-GO - - - 1 - 10336 - Gonzo, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/3/33541.jpg - Last Exile: Ginyoku no Fam - - - 6 - 19137 - - http://cdn.myanimelist.net/images/anime/5/51349.jpg - EDEN - - - 1 - 11541 - - http://cdn.myanimelist.net/images/anime/7/33869.jpg - Ad Lib Anime Kenkyuujo - - - 4 - 10301 - Xebec - http://cdn.myanimelist.net/images/anime/8/28328.jpg - Rio: Rainbow Gate Special - - - 6 - 12121 - - http://cdn.myanimelist.net/images/anime/9/34227.jpg - Towa no Kizuna - - - 3 - 11673 - TMS Entertainment - http://cdn.myanimelist.net/images/anime/12/32545.jpg - Hal no Fue - - - 3 - 10693 - Production I.G, Jinnis Animation Studios - http://cdn.myanimelist.net/images/anime/6/37085.jpg - Appleseed XIII Remix Movie 2: Yogen - - - 4 - 11266 - Aniplex, A-1 Pictures, Aniplex of America - http://cdn.myanimelist.net/images/anime/3/34051.jpg - Ao no Exorcist: Kuro no Iede - - - 4 - 10905 - Satelight - http://cdn.myanimelist.net/images/anime/3/30109.jpg - Ikoku Meiro no Croisée Picture Drama - - - 4 - 11113 - Production I.G, NIS America, Inc. - http://cdn.myanimelist.net/images/anime/7/30759.jpg - Usagi Drop Specials - - - 4 - 12255 - Lerche - http://cdn.myanimelist.net/images/anime/10/34509.jpg - Carnival Phantasm: Illya's Castle - - - 4 - 12065 - Arms, Genco, Media Factory, Hobby Japan - http://cdn.myanimelist.net/images/anime/6/34021.jpg - Queen's Blade OVA Specials - - - 3 - 10821 - Toei Animation - http://cdn.myanimelist.net/images/anime/13/30264.jpg - Suite Precure♪ Movie: Torimodose! Kokoro ga Tsunaku Kiseki no Melody♪ - - - 2 - 10924 - Arms, Genco, Media Factory, Hobby Japan - http://cdn.myanimelist.net/images/anime/3/30145.jpg - Queen's Blade OVA - - - 4 - 11237 - Shaft, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/9/34085.jpg - Hidamari Sketch x SP - - - 3 - 12035 - - http://cdn.myanimelist.net/images/anime/13/33977.jpg - Dwaejiui Wang - - - 3 - 10716 - Bones, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/3/34343.jpg - Towa no Quon 5: Souzetsu no Raifuku - - - 6 - 13153 - - http://cdn.myanimelist.net/images/anime/11/36829.jpg - Renkyori Enai - - - 2 - 11255 - Arms - http://cdn.myanimelist.net/images/anime/2/33853.jpg - Ikkitousen: Shuugaku Toushi Keppuuroku - - - 4 - 12665 - Dogakobo, Pony Canyon, DAX Production - http://cdn.myanimelist.net/images/anime/10/35541.jpg - Yuru Yuri: Doushite, Tomaranai, Tokimeki, Doki Doki, Paradox, Eternal - - - 3 - 11531 - Bones, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/5/35071.jpg - UN-GO episode:0 Inga-ron - - - 3 - 10592 - Sunrise - http://cdn.myanimelist.net/images/anime/9/36831.jpg - Scryed Alteration I: Tao - - - 2 - 8995 - ufotable - http://cdn.myanimelist.net/images/anime/6/35809.jpg - Tales of Symphonia The Animation: Sekai Tougou-hen - - - 4 - 12709 - ufotable - http://cdn.myanimelist.net/images/anime/10/35705.jpg - Tales of Symphonia The Animation: Sekai Tougou-hen Specials - - - 2 - 11569 - Toei Animation - http://cdn.myanimelist.net/images/anime/12/32263.jpg - Precure All-Stars DX the Dance Live: Miracle Dance Stage e Youkoso - - - 2 - 12187 - Lerche - http://cdn.myanimelist.net/images/anime/10/34405.jpg - Carnival Phantasm EX Season - - - 3 - 10717 - Bones, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/10/35131.jpg - Towa no Quon 6: Towa no Quon - - - 1 - 12181 - Crossphere - http://cdn.myanimelist.net/images/anime/7/34393.jpg - Nippon Omoshiro Mukashi Banashi - - - 1 - 10958 - DLE - http://cdn.myanimelist.net/images/anime/6/36265.jpg - High Score - + + + + Fall 2011 + 1380254460 + + + 3 + 10624 + 6269 + Starchild Records, GoHands, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/11/29197.jpg + Mardock Scramble: The Second Combustion + + + 3 + 10731 + 6321 + Production I.G, NAS, M.S.C + http://cdn.myanimelist.net/images/anime/3/32079.jpg + Prince of Tennis: Eikokushiki Teikyuu Shiro Kessen! + + + 2 + 10582 + 6251 + Diomedea + http://cdn.myanimelist.net/images/anime/11/30853.jpg + Astarotte no Omocha! EX + + + 5 + 11103 + 6461 + TNK, Kinema Citrus + http://cdn.myanimelist.net/images/anime/7/37953.jpg + Busou Shinki Moon Angel + + + 3 + 16756 + 7509 + Vasoon Animation + http://cdn.myanimelist.net/images/anime/8/45068.jpg + Kuiba + + + 3 + 10389 + 6160 + Production I.G, GKids + http://cdn.myanimelist.net/images/anime/2/43557.jpg + Momo e no Tegami + + + 3 + 10715 + 6312 + Bones, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/11/33219.jpg + Towa no Quon 4: Guren no Shoushin + + + 6 + 19727 + + + http://cdn.myanimelist.net/images/anime/5/52743.jpg + Nexus + + + 6 + 11583 + 6541 + Manglobe + http://cdn.myanimelist.net/images/anime/3/32325.jpg + Natsuiro Surprise + + + 2 + 10805 + 6359 + Geneon Universal Entertainment, Manglobe, Shogakukan Productions, Shogakukan Music & Digital Entertainment + http://cdn.myanimelist.net/images/anime/12/32297.jpg + Kami nomi zo Shiru Sekai: 4-nin to Idol + + + 3 + 10408 + 6169 + Aniplex, Brains Base + http://cdn.myanimelist.net/images/anime/8/38229.jpg + Hotarubi no Mori e + + + 1 + 11017 + 6440 + Sunrise + http://cdn.myanimelist.net/images/anime/3/32193.jpg + Battle Spirits: Heroes + + + 4 + 10832 + 6375 + Satelight + http://cdn.myanimelist.net/images/anime/9/31995.jpg + Ikoku Meiro no Croisée: Yune & Alice + + + 4 + 10834 + 6376 + Silver Link + http://cdn.myanimelist.net/images/anime/7/41417.jpg + Baka to Test to Shoukanjuu: Spinout! Sore ga Bokura no Nichijou + + + 4 + 13681 + 7014 + AIC Plus+ + http://cdn.myanimelist.net/images/anime/12/37991.jpg + Nekogami Yaoyorozu Specials + + + 4 + 11715 + 6574 + Brains Base, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/8/32717.jpg + Kamisama Dolls Specials + + + 4 + 11731 + 6577 + + http://cdn.myanimelist.net/images/anime/9/32751.jpg + Chi-Sui Maru Specials + + + 6 + 11507 + 6524 + Manglobe + http://cdn.myanimelist.net/images/anime/2/32091.jpg + Kami nomi zo Shiru Sekai: Nonstop!! Hunters + + + 2 + 10897 + 6397 + AIC Build, Bushiroad Inc. + http://cdn.myanimelist.net/images/anime/8/30097.jpg + Boku wa Tomodachi ga Sukunai Episode 0 + + + 2 + 16377 + 7415 + CoMix Wave + http://cdn.myanimelist.net/images/anime/6/44191.jpg + Peeping Life: The Perfect Extension + + + 4 + 11083 + 6010 + Studio Deen + http://cdn.myanimelist.net/images/anime/12/31455.jpg + Nurarihyon no Mago: Sennen Makyou Recaps + + + 2 + 13993 + 7066 + Studio Blanc + http://cdn.myanimelist.net/images/anime/10/38821.jpg + Goulart Knights: Evoked the Beginning White + + + 1 + 10378 + 6156 + TV Tokyo, Diomedea, Nomad, Lantis, Pony Canyon, Production Reed, Encourage Films, TV Tokyo Music, Studio Jack + http://cdn.myanimelist.net/images/anime/6/30120.jpg + Shinryaku!? Ika Musume + + + 4 + 11505 + 6523 + Satelight + http://cdn.myanimelist.net/images/anime/11/32089.jpg + Kiss Dum Special + + + 4 + 12579 + 6767 + Zexcs + http://cdn.myanimelist.net/images/anime/5/35285.jpg + Itsuka Tenma no Kuro Usagi Picture Drama + + + 1 + 10521 + 6220 + Aniplex, A-1 Pictures, Yomiuri Telecasting Corporation, Rakuonsha + http://cdn.myanimelist.net/images/anime/4/33739.jpg + Working'!! + + + 3 + 9000 + 5567 + Madhouse Studios, TV Tokyo, TMS Entertainment, Movic, NIS America, Inc. + http://cdn.myanimelist.net/images/anime/3/36903.jpg + Toaru Hikuushi e no Tsuioku + + + 1 + 10030 + 6001 + J.C. Staff, NHK, Media Blasters + http://cdn.myanimelist.net/images/anime/3/34923.jpg + Bakuman. 2 + + + 1 + 10578 + 6249 + Starchild Records, FUNimation Entertainment, Silver Link + http://cdn.myanimelist.net/images/anime/13/32285.jpg + + + + 1 + 11061 + 6448 + Madhouse Studios, VAP + http://cdn.myanimelist.net/images/anime/11/33657.jpg + Hunter x Hunter (2011) + + + 1 + 10087 + 6028 + Aniplex, ufotable, Nitroplus, Aniplex of America, seikaisha, Notes + http://cdn.myanimelist.net/images/anime/8/29490.jpg + Fate/Zero + + + 1 + 11385 + 6503 + Toei Animation + http://cdn.myanimelist.net/images/anime/8/31793.jpg + Digimon Xros Wars: Toki wo Kakeru Shounen Hunter-tachi + + + 1 + 11177 + 6468 + SynergySP, TV Tokyo Music + http://cdn.myanimelist.net/images/anime/2/32803.jpg + Cross Fight B-Daman + + + 1 + 10456 + 6187 + Sunrise, Lantis, Sentai Filmworks, ASCII Media Works + http://cdn.myanimelist.net/images/anime/4/33743.jpg + Kyoukaisenjou no Horizon + + + 1 + 10213 + 6081 + Sentai Filmworks, Lerche + http://cdn.myanimelist.net/images/anime/4/32541.jpg + Maji de Watashi ni Koi Shinasai! + + + 1 + 10997 + 6434 + + http://cdn.myanimelist.net/images/anime/4/30525.jpg + Fujilog 2nd Season + + + 1 + 9981 + 5978 + Sunrise, NHK Enterprises, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/12/34045.jpg + Phi Brain: Kami no Puzzle + + + 1 + 10232 + 6087 + TYO Animations, BIGLOBE + http://cdn.myanimelist.net/images/anime/13/45368.jpg + Tamayura: Hitotose + + + 1 + 11547 + 6535 + Milky Cartoon, LMD, G-mode + http://cdn.myanimelist.net/images/anime/11/39031.jpg + Sengoku☆Paradise Kiwami + + + 4 + 10845 + 6379 + Hoods Entertainment + http://cdn.myanimelist.net/images/anime/3/29932.jpg + Manyuu Hikenchou Specials + + + 4 + 12239 + 6693 + Hoods Entertainment + http://cdn.myanimelist.net/images/anime/8/34477.jpg + Manyuu Hikenchou Picture Drama + + + 1 + 10460 + 6191 + J.C. Staff, TV Tokyo, Aniplex, Square Enix, NAS, Movic, Kimi To Boku Production Partners + http://cdn.myanimelist.net/images/anime/4/34949.jpg + Kimi to Boku. + + + 1 + 10800 + 6355 + Madhouse Studios, VAP + http://cdn.myanimelist.net/images/anime/3/35749.jpg + Chihayafuru + + + 1 + 11615 + 6549 + DAX Production, Takeshobo, Seven + http://cdn.myanimelist.net/images/anime/7/32933.jpg + Morita-san wa Mukuchi. 2 + + + 5 + 12411 + 6725 + Bones, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/11/34935.jpg + UN-GO: Inga Nikki + + + 1 + 10397 + 6165 + Manglobe, Frontier Works, Lantis, Media Factory, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/9/39303.jpg + Mashiroiro Symphony: The Color of Lovers + + + 1 + 9936 + 5949 + AIC, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/9/34955.jpg + Maken-Ki! + + + 1 + 10588 + 6255 + Aniplex, AIC A.S.T.A., Sentai Filmworks, ASCII Media Works, Index + http://cdn.myanimelist.net/images/anime/4/29107.jpg + Persona 4 The Animation + + + 1 + 10719 + 6316 + FUNimation Entertainment, Media Factory, TBS, AIC Build, Bushiroad Inc. + http://cdn.myanimelist.net/images/anime/8/32873.jpg + Boku wa Tomodachi ga Sukunai + + + 1 + 11123 + 6464 + Studio Deen, The Right Stuf International + http://cdn.myanimelist.net/images/anime/8/34871.jpg + Sekaiichi Hatsukoi 2 + + + 1 + 6773 + 4709 + J.C. Staff, Geneon Universal Entertainment, FUNimation Entertainment, Warner Bros., ASCII Media Works, Showgate + http://cdn.myanimelist.net/images/anime/9/32539.jpg + Shakugan no Shana III (Final) + + + 2 + 10418 + 6172 + Manglobe, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/8/33033.jpg + Deadman Wonderland OVA + + + 1 + 10808 + 6361 + Sunrise, Lantis, Mainichi Broadcasting, Sony Music Entertainment + http://cdn.myanimelist.net/images/anime/12/42535.jpg + Mobile Suit Gundam AGE + + + 1 + 10620 + 6266 + FUNimation Entertainment, Lantis, Kadokawa Shoten, Asread, Rakuonsha, Kadokawa Pictures Japan, The Klock Worx, chara-ani.com, 12 Diary Holders, Dwango, Sakura Create + http://cdn.myanimelist.net/images/anime/13/33465.jpg + Mirai Nikki (TV) + + + 1 + 10396 + 6164 + AT-X, David Production + http://cdn.myanimelist.net/images/anime/3/32315.jpg + Ben-To + + + 2 + 10260 + 6104 + Kinema Citrus, Sentai Filmworks, Nihon Falcom, Bushiroad Inc., Showgate + http://cdn.myanimelist.net/images/anime/11/35775.jpg + Eiyuu Densetsu: Sora no Kiseki + + + 2 + 11355 + 6498 + CoMix Wave, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/5/32057.jpg + Kono Danshi, Uchuujin to Tatakaemasu. + + + 1 + 11457 + 6512 + Shogakukan Productions, SynergySP, Half H.P Studio + http://cdn.myanimelist.net/images/anime/6/35927.jpg + Chibi☆Devi! + + + 1 + 11809 + 6607 + Strawberry Meets Pictures + http://cdn.myanimelist.net/images/anime/8/34963.jpg + gdgd Fairies + + + 1 + 10793 + 6349 + Production I.G, Aniplex, FUNimation Entertainment, Movic, Fuji TV, Fuji Pacific Music Publishing + http://cdn.myanimelist.net/images/anime/8/33713.jpg + Guilty Crown + + + 1 + 10798 + 6353 + Bones, Dentsu, Fuji TV, Toho Company, Sentai Filmworks, Sony Music Entertainment, Fuji Pacific Music Publishing + http://cdn.myanimelist.net/images/anime/12/33009.jpg + UN-GO + + + 1 + 10336 + 6134 + Gonzo, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/3/33541.jpg + Last Exile: Ginyoku no Fam + + + 6 + 19137 + + + http://cdn.myanimelist.net/images/anime/5/51349.jpg + EDEN + + + 1 + 11541 + 6532 + + http://cdn.myanimelist.net/images/anime/7/33869.jpg + Ad Lib Anime Kenkyuujo + + + 4 + 10301 + 6119 + Xebec + http://cdn.myanimelist.net/images/anime/8/28328.jpg + Rio: Rainbow Gate Special + + + 6 + 12121 + 6668 + + http://cdn.myanimelist.net/images/anime/9/34227.jpg + Towa no Kizuna + + + 3 + 11673 + 6558 + TMS Entertainment + http://cdn.myanimelist.net/images/anime/12/32545.jpg + Hal no Fue + + + 3 + 10693 + 6298 + Production I.G, Jinnis Animation Studios + http://cdn.myanimelist.net/images/anime/6/37085.jpg + Appleseed XIII Remix Movie 2: Yogen + + + 4 + 11266 + 6484 + Aniplex, A-1 Pictures, Aniplex of America + http://cdn.myanimelist.net/images/anime/3/34051.jpg + Ao no Exorcist: Kuro no Iede + + + 4 + 10905 + 6400 + Satelight + http://cdn.myanimelist.net/images/anime/3/30109.jpg + Ikoku Meiro no Croisée Picture Drama + + + 4 + 11113 + 6463 + Production I.G, NIS America, Inc. + http://cdn.myanimelist.net/images/anime/7/30759.jpg + Usagi Drop Specials + + + 4 + 12255 + 6695 + Lerche + http://cdn.myanimelist.net/images/anime/10/34509.jpg + Carnival Phantasm: Illya's Castle + + + 4 + 12065 + 6658 + Arms, Genco, Media Factory, Hobby Japan + http://cdn.myanimelist.net/images/anime/6/34021.jpg + Queen's Blade OVA Specials + + + 3 + 10821 + 6368 + Toei Animation + http://cdn.myanimelist.net/images/anime/13/30264.jpg + Suite Precure♪ Movie: Torimodose! Kokoro ga Tsunaku Kiseki no Melody♪ + + + 2 + 10924 + 6409 + Arms, Genco, Media Factory, Hobby Japan + http://cdn.myanimelist.net/images/anime/3/30145.jpg + Queen's Blade OVA + + + 4 + 11237 + 6479 + Shaft, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/9/34085.jpg + Hidamari Sketch x SP + + + 3 + 12035 + 6649 + + http://cdn.myanimelist.net/images/anime/13/33977.jpg + Dwaejiui Wang + + + 3 + 10716 + 6313 + Bones, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/3/34343.jpg + Towa no Quon 5: Souzetsu no Raifuku + + + 6 + 13153 + + + http://cdn.myanimelist.net/images/anime/11/36829.jpg + Renkyori Enai + + + 2 + 11255 + 6483 + Arms + http://cdn.myanimelist.net/images/anime/2/33853.jpg + Ikkitousen: Shuugaku Toushi Keppuuroku + + + 4 + 12665 + 6784 + Dogakobo, Pony Canyon, DAX Production + http://cdn.myanimelist.net/images/anime/10/35541.jpg + Yuru Yuri: Doushite, Tomaranai, Tokimeki, Doki Doki, Paradox, Eternal + + + 3 + 11531 + 6529 + Bones, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/5/35071.jpg + UN-GO episode:0 Inga-ron + + + 3 + 10592 + 6258 + Sunrise + http://cdn.myanimelist.net/images/anime/9/36831.jpg + Scryed Alteration I: Tao + + + 2 + 8995 + 5565 + ufotable + http://cdn.myanimelist.net/images/anime/6/35809.jpg + Tales of Symphonia The Animation: Sekai Tougou-hen + + + 4 + 12709 + 6795 + ufotable + http://cdn.myanimelist.net/images/anime/10/35705.jpg + Tales of Symphonia The Animation: Sekai Tougou-hen Specials + + + 2 + 11569 + 6538 + Toei Animation + http://cdn.myanimelist.net/images/anime/12/32263.jpg + Precure All-Stars DX the Dance Live: Miracle Dance Stage e Youkoso + + + 2 + 12187 + 6685 + Lerche + http://cdn.myanimelist.net/images/anime/10/34405.jpg + Carnival Phantasm EX Season + + + 3 + 10717 + 6314 + Bones, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/10/35131.jpg + Towa no Quon 6: Towa no Quon + + + 1 + 12181 + 6683 + Crossphere + http://cdn.myanimelist.net/images/anime/7/34393.jpg + Nippon Omoshiro Mukashi Banashi + + + 1 + 10958 + 6426 + DLE + http://cdn.myanimelist.net/images/anime/6/36265.jpg + High Score + \ No newline at end of file diff --git a/data/db/season/2011_spring.xml b/data/db/season/2011_spring.xml index 0fe5ce535..7b9d5d942 100644 --- a/data/db/season/2011_spring.xml +++ b/data/db/season/2011_spring.xml @@ -1,924 +1,1055 @@ - - - - Spring 2011 - 1380217365 - - - 1 - 7081 - TV Tokyo, Oriental Light and Magic, Dentsu, Dentsu Entertainment USA - http://cdn.myanimelist.net/images/anime/10/32037.jpg - Danball Senki - - - 2 - 9935 - SynergySP - http://cdn.myanimelist.net/images/anime/6/35601.jpg - Chocolat no Mahou - - - 3 - 10500 - Telecom Animation Film - http://cdn.myanimelist.net/images/anime/5/29065.jpg - Ojii-san no Lamp - - - 3 - 10501 - P.A. Works - http://cdn.myanimelist.net/images/anime/12/29028.jpg - Bannou Yasai Ninninman - - - 3 - 10502 - Production I.G - http://cdn.myanimelist.net/images/anime/5/28893.jpg - Tansu Warashi. - - - 3 - 10016 - Ascension - http://cdn.myanimelist.net/images/anime/11/29055.jpg - Kizuna Ichigeki - - - 3 - 10534 - TV Asahi, Asatsu DK, Shin-Ei Animation - http://cdn.myanimelist.net/images/anime/5/28944.jpg - Doraemon: Nobita and the New Steel Troops - Angel Wings - - - 4 - 10076 - J.C. Staff, Nomad, Lantis, DAX Production, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/2/29811.jpg - Kämpfer für die Liebe - - - 5 - 10448 - - http://cdn.myanimelist.net/images/anime/5/29062.jpg - Rain Town - - - 5 - 16718 - Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/5/44956.jpg - SKET Dance: SD Character Flash Anime - - - 3 - 10114 - Ajia-Do - http://cdn.myanimelist.net/images/anime/5/34395.jpg - Nintama Rantarou Movie: Ninjutsu Gakuen Zenin Shutsudou! no Dan - - - 2 - 8857 - Kyoto Animation, Animation Do - http://cdn.myanimelist.net/images/anime/6/25521.jpg - Nichijou Episode 0 - - - 4 - 10477 - DLE - http://cdn.myanimelist.net/images/anime/6/28784.jpg - Haiyoru! Nyaruani: Remember My Love(craft-sensei) Special - - - 4 - 9734 - Kyoto Animation, Pony Canyon, Sentai Filmworks, Animation Do - http://cdn.myanimelist.net/images/anime/7/26965.jpg - K-On!!: Keikaku! - - - 2 - 10406 - Studio Puyukai - http://cdn.myanimelist.net/images/anime/9/28590.jpg - Spelunker Sensei - - - 3 - 17233 - - http://cdn.myanimelist.net/images/anime/13/46023.jpg - The House - - - 3 - 9999 - Toei Animation - http://cdn.myanimelist.net/images/anime/4/32455.jpg - One Piece 3D: Mugiwara Chase - - - 3 - 10074 - Toei Animation - http://cdn.myanimelist.net/images/anime/6/28914.jpg - Toriko 3D: Kaimaku Gourmet Adventure!! - - - 3 - 9979 - Toei Animation - http://cdn.myanimelist.net/images/anime/12/27934.jpg - Precure All Stars Movie DX3: Mirai ni Todoke! Sekai wo Tsunagu Niji-iro no Hana - - - 2 - 10545 - AIC - http://cdn.myanimelist.net/images/anime/11/28972.jpg - The Epic Of ZektBach - - - 4 - 10407 - - http://cdn.myanimelist.net/images/anime/6/28586.jpg - Shimanchu MiRiKa - - - 2 - 8063 - Studio Deen - http://cdn.myanimelist.net/images/anime/11/35863.jpg - Sekaiichi Hatsukoi OVA - - - 3 - 17104 - - http://cdn.myanimelist.net/images/anime/7/45720.jpg - Lolling Seutajeu (Movie) - - - 4 - 10497 - Studio Deen, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/13/28864.jpg - Hetalia World Series Extra Episodes - - - 2 - 9088 - Sentai Filmworks, Anpro - http://cdn.myanimelist.net/images/anime/7/25186.jpg - Saiyuuki Gaiden - - - 4 - 10504 - Bones - http://cdn.myanimelist.net/images/anime/11/28897.jpg - Gosick Recap - - - 1 - 10506 - Kachidoki Studio - http://cdn.myanimelist.net/images/anime/2/28900.jpg - Shiawase Haitatsu Taneko - - - 2 - 9774 - DAX Production, Studio Gram, Seven, Dream Creation - http://cdn.myanimelist.net/images/anime/12/29056.jpg - Morita-san wa Mukuchi - - - 3 - 10090 - Production I.G - http://cdn.myanimelist.net/images/anime/4/29259.jpg - Ghost in the Shell: Stand Alone Complex - Solid State Society 3D - - - 3 - 10092 - Production I.G, Xebec, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/11/27832.jpg - Break Blade 6: Doukoku no Toride - - - 3 - 10405 - Production I.G - http://cdn.myanimelist.net/images/anime/3/33549.jpg - Xi Avant - - - 1 - 13457 - Egg - http://cdn.myanimelist.net/images/anime/10/37603.jpg - Tomodachi 8-nin - - - 2 - 9741 - AIC Plus+ - http://cdn.myanimelist.net/images/anime/5/27379.jpg - Nana to Kaoru - - - 2 - 10516 - - http://cdn.myanimelist.net/images/anime/4/28918.jpg - Anemone - - - 4 - 10172 - FUNimation Entertainment, Media Factory - http://cdn.myanimelist.net/images/anime/9/28079.jpg - Freezing Specials - - - 2 - 9924 - AIC, The Right Stuf International - http://cdn.myanimelist.net/images/anime/11/27462.jpg - Shukufuku no Campanella (OVA) - - - 5 - 13501 - - http://cdn.myanimelist.net/images/anime/12/37695.jpg - Cofun Gal no Coffy (ONA) - - - 1 - 6919 - Madhouse Studios, Sony Pictures Entertainment, Marvel Entertainment - http://cdn.myanimelist.net/images/anime/8/30014.jpg - X-Men - - - 5 - 10519 - Nitroplus - http://cdn.myanimelist.net/images/anime/4/28921.jpg - Mahou Shoujo Sonico★Magica - - - 5 - 10532 - A-1 Pictures - http://cdn.myanimelist.net/images/anime/12/28941.jpg - Working'!! Announcement Specials - - - 1 - 10499 - TV Tokyo - http://cdn.myanimelist.net/images/anime/10/28937.jpg - Tottoko Hamtaro Dechu - - - 1 - 10524 - Shogakukan Productions - http://cdn.myanimelist.net/images/anime/2/28924.jpg - Duel Masters Victory - - - 1 - 10856 - Shogakukan Music & Digital Entertainment - http://cdn.myanimelist.net/images/anime/13/29984.jpg - Penguin no Mondai DX? - - - 1 - 10155 - Aniplex, Seven Arcs - http://cdn.myanimelist.net/images/anime/7/29363.jpg - Dog Days - - - 1 - 10033 - Toei Animation, FUNimation Entertainment, Yomiko Advertising, Fuji TV - http://cdn.myanimelist.net/images/anime/13/30135.jpg - Toriko - - - 1 - 9289 - Bandai Visual, Yomiuri Telecasting Corporation, Lantis, P.A. Works, Pony Canyon, NIS America, Inc., Showgate - http://cdn.myanimelist.net/images/anime/3/28967.jpg - Hanasaku Iroha - - - 1 - 10349 - Digital Media Lab - http://cdn.myanimelist.net/images/anime/5/45917.jpg - Suzy's Zoo: Daisuki! Witzy - - - 1 - 10370 - SynergySP - http://cdn.myanimelist.net/images/anime/11/29013.jpg - Metal Fight Beyblade 4D - - - 1 - 10165 - Kyoto Animation, Lantis, Kadokawa Shoten, Movic, Rakuonsha, Bandai Entertainment, Kadokawa Pictures Japan, The Klock Worx, Animation Do - http://cdn.myanimelist.net/images/anime/9/29714.jpg - Nichijou - - - 1 - 10444 - Toei Animation - http://cdn.myanimelist.net/images/anime/11/28688.jpg - Digimon Xros Wars: Aku no Death General to Nanatsu no Oukoku - - - 1 - 9941 - Sunrise, Viz Media - http://cdn.myanimelist.net/images/anime/13/29466.jpg - Tiger & Bunny - - - 1 - 10014 - Wao World - http://cdn.myanimelist.net/images/anime/5/41117.jpg - Shouwa Monogatari - - - 1 - 10533 - - http://cdn.myanimelist.net/images/anime/13/28943.jpg - Fujilog - - - 1 - 10348 - Jinnis Animation Studios - http://cdn.myanimelist.net/images/anime/4/28791.jpg - Fireball Charming - - - 1 - 9922 - Nomad, FUNimation Entertainment, Lantis, Delphi Sound, Marvelous AQL, Studio Jack - http://cdn.myanimelist.net/images/anime/11/32047.jpg - Oretachi ni Tsubasa wa Nai: Under the Innocent Sky. - - - 1 - 9969 - Sunrise, TV Tokyo, Aniplex, Dentsu, Trinity Sound, Miracle Robo, Studio Jack - http://cdn.myanimelist.net/images/anime/4/50361.jpg - Gintama' - - - 1 - 10308 - TMS Entertainment, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/2/29775.jpg - Sengoku Otome: Momoiro Paradox - - - 1 - 10324 - Frontier Works, Gathering - http://cdn.myanimelist.net/images/anime/10/28398.jpg - Tono to Issho: Gantai no Yabou - - - 1 - 10271 - Madhouse Studios - http://cdn.myanimelist.net/images/anime/10/30599.jpg - Gyakkyou Burai Kaiji: Hakairoku Hen - - - 1 - 9253 - Frontier Works, FUNimation Entertainment, Media Factory, Movic, AT-X, White Fox, Kadokawa Pictures Japan, Nitroplus - http://cdn.myanimelist.net/images/anime/11/41011.jpg - Steins;Gate - - - 1 - 10347 - Shogakukan Music & Digital Entertainment - http://cdn.myanimelist.net/images/anime/3/32993.jpg - Happy Kappy - - - 4 - 10483 - Production I.G, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/7/28823.jpg - Sengoku Basara Two: Ryuko, Itadaki no Chikai! Atsuki Mirai e Kakeru Tamashii!! - - - 1 - 9996 - Bee Train, NHK, Sogo Vision - http://cdn.myanimelist.net/images/anime/5/28799.jpg - Hyouge Mono - - - 1 - 9863 - TV Tokyo, Avex Entertainment, Dentsu, Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/3/28669.jpg - SKET Dance - - - 1 - 9624 - Gathering - http://cdn.myanimelist.net/images/anime/9/27498.jpg - 30-sai no Hoken Taiiku - - - 1 - 9493 - Brains Base, NIS America, Inc., Dynamic Planning - http://cdn.myanimelist.net/images/anime/13/28978.jpg - Dororon Enma-kun Meeramera - - - 1 - 9776 - Aniplex, Dentsu, Mainichi Broadcasting, Sentai Filmworks, Studio Gokumi - http://cdn.myanimelist.net/images/anime/5/29687.jpg - A-Channel - - - 1 - 10109 - Xebec, Geneon Universal Entertainment, Lantis, AT-X, The Klock Worx, Studio Mausu - http://cdn.myanimelist.net/images/anime/4/29091.jpg - Softenni - - - 1 - 10187 - Xebec - http://cdn.myanimelist.net/images/anime/3/28089.jpg - Hen Zemi (TV) - - - 1 - 10216 - Production I.G - http://cdn.myanimelist.net/images/anime/12/28788.jpg - Yondemasu yo, Azazel-san. (TV) - - - 1 - 9712 - Shaft, Sentai Filmworks, TV Tokyo Music - http://cdn.myanimelist.net/images/anime/2/28757.jpg - Maria†Holic Alive - - - 1 - 10257 - Nomad, Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/9/36423.jpg - Pretty Rhythm: Aurora Dream - - - 1 - 10359 - TV Tokyo, Studio Comet, Studio Jack - http://cdn.myanimelist.net/images/anime/7/28699.jpg - Jewelpet Sunshine - - - 1 - 9926 - Studio Deen, Lantis - http://cdn.myanimelist.net/images/anime/6/29763.jpg - Sekaiichi Hatsukoi - - - 1 - 10346 - Toei Animation - http://cdn.myanimelist.net/images/anime/2/28438.jpg - Ring ni Kakero 1: Sekai Taikai Hen - - - 1 - 9736 - Diomedea, Kadokawa Shoten, Pony Canyon, DAX Production, The Klock Worx, ASCII Media Works, Astarotte no Omocha! Production Committee - http://cdn.myanimelist.net/images/anime/6/28794.jpg - Astarotte no Omocha! - - - 1 - 10015 - TV Tokyo, Marvelous Entertainment, Konami, NAS, Digital Works, 4Kids Entertainment - http://cdn.myanimelist.net/images/anime/4/40295.jpg - Yu-Gi-Oh! Zexal - - - 1 - 10079 - Dentsu, Marvelous Entertainment, Dogakobo, Pony Canyon, feng, PRA, Jumondo - http://cdn.myanimelist.net/images/anime/11/29810.jpg - Hoshizora e Kakaru Hashi - - - 1 - 10073 - Hoods Entertainment, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/8/28815.jpg - Seikon no Qwaser II - - - 1 - 10080 - TV Tokyo, Geneon Universal Entertainment, Manglobe, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/11/30030.jpg - Kami nomi zo Shiru Sekai II - - - 1 - 10539 - DLE - http://cdn.myanimelist.net/images/anime/10/28949.jpg - Hato no Oyomesan - - - 1 - 10540 - DLE - http://cdn.myanimelist.net/images/anime/6/28950.jpg - Genki!! Ekoda-chan - - - 1 - 10541 - DLE - http://cdn.myanimelist.net/images/anime/4/28951.jpg - Shuukan Shimakou - - - 2 - 9793 - A-1 Pictures, Bridge - http://cdn.myanimelist.net/images/anime/5/27146.jpg - Senjou no Valkyria 3: Tagatame no Juusou - - - 1 - 10338 - DLE - http://cdn.myanimelist.net/images/anime/7/29163.jpg - Honto ni Atta! Reibai Sensei - - - 1 - 10633 - DLE - http://cdn.myanimelist.net/images/anime/8/29262.jpg - Shiodome Cable TV - - - 1 - 10459 - DLE - http://cdn.myanimelist.net/images/anime/9/28722.jpg - Puu-Neko - - - 1 - 9989 - Aniplex, Dentsu, A-1 Pictures, Fuji TV, NIS America, Inc., Fuji Pacific Music Publishing - http://cdn.myanimelist.net/images/anime/12/28009.jpg - Ano Hi Mita Hana no Namae wo Bokutachi wa Mada Shiranai. - - - 5 - 10850 - Shin-Ei Animation - http://cdn.myanimelist.net/images/anime/13/29970.jpg - Seibu Tetsudou Ekiin Tako-chan - - - 2 - 10119 - GoHands - http://cdn.myanimelist.net/images/anime/9/41873.jpg - Seitokai Yakuindomo OVA - - - 1 - 9379 - Shaft, Starchild Records, NIS America, Inc. - http://cdn.myanimelist.net/images/anime/11/28715.jpg - Denpa Onna to Seishun Otoko - - - 1 - 10163 - FUNimation Entertainment, Tatsunoko Productions, Fuji TV, Jumondo, Sony Music Entertainment, Fuji Pacific Music Publishing - http://cdn.myanimelist.net/images/anime/5/50551.jpg - C: The Money of Soul and Possibility Control - - - 1 - 8630 - J.C. Staff, FUNimation Entertainment, TBS - http://cdn.myanimelist.net/images/anime/9/30095.jpg - Hidan no Aria - - - 1 - 8143 - Iyasakadou Film - http://cdn.myanimelist.net/images/anime/3/24671.jpg - Sockies: Frontier Quest - - - 2 - 9982 - Satelight, A-1 Pictures - http://cdn.myanimelist.net/images/anime/6/28494.jpg - Fairy Tail OVA - - - 2 - 10531 - Shogakukan Productions, Tokyo Movie Shinsha, Toho Company - http://cdn.myanimelist.net/images/anime/6/28938.jpg - Detective Conan Magic File 5: Niigata - Tokyo Omiyage Capriccio - - - 3 - 10116 - Shin-Ei Animation - http://cdn.myanimelist.net/images/anime/9/27894.jpg - Crayon Shin-chan Movie 19: Arashi wo Yobu Ougon no Spy Daisakusen - - - 3 - 9963 - Shogakukan Productions, Tokyo Movie Shinsha, Toho Company - http://cdn.myanimelist.net/images/anime/9/32035.jpg - Detective Conan Movie 15: Quarter of Silence - - - 1 - 9919 - Aniplex, Dentsu, A-1 Pictures, Mainichi Broadcasting, Movic, Aniplex of America, Sakura Create - http://cdn.myanimelist.net/images/anime/11/29173.jpg - Ao no Exorcist - - - 1 - 6880 - Manglobe, FUNimation Entertainment, AMG MUSIC - http://cdn.myanimelist.net/images/anime/12/29882.jpg - Deadman Wonderland - - - 4 - 10536 - Production I.G - http://cdn.myanimelist.net/images/anime/10/28946.jpg - Kimi ni Todoke 2nd Season Specials - - - 6 - 11511 - - http://cdn.myanimelist.net/images/anime/4/32107.jpg - Evidence - - - 5 - 10766 - TMS Entertainment - http://cdn.myanimelist.net/images/anime/7/29671.jpg - Detective Conan vs. Wooo - - - 1 - 10513 - Kanaban Graphics - http://cdn.myanimelist.net/images/anime/7/54015.jpg - Usavich IV - - - 1 - 9693 - Production I.G, NHK, NHK Enterprises - http://cdn.myanimelist.net/images/anime/9/29166.jpg - Moshidora - - - 2 - 9515 - Madhouse Studios, Sentai Filmworks, H.O.T.D Production Committee, Showgate - http://cdn.myanimelist.net/images/anime/10/54427.jpg - Highschool of the Dead: Drifters of the Dead - - - 6 - 10679 - Media Factory - http://cdn.myanimelist.net/images/anime/2/29426.jpg - Maria†Holic: Run Run Riru Ran Ran Rara - - - 4 - 10739 - Gainax, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/6/29589.jpg - Panty & Stocking in Sanitarybox - - - 3 - 10261 - Lapis, Elevenarts - http://cdn.myanimelist.net/images/anime/10/32533.jpg - Toufu Kozou - - - 3 - 8487 - Studio Pierrot, Sony Music Entertainment - http://cdn.myanimelist.net/images/anime/3/28871.jpg - Onigamiden - - - 4 - 9925 - AIC, Pony Canyon - http://cdn.myanimelist.net/images/anime/5/27464.jpg - Amagami SS: Imouto - - - 4 - 10775 - Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/11/29695.jpg - SKET Dance: Demystifying Special - - - 4 - 9888 - Diomedea - http://cdn.myanimelist.net/images/anime/7/27364.jpg - Shinryaku! Ika Musume Specials - - - 1 - 10507 - TV Tokyo, Oriental Light and Magic, Dentsu, Half H.P Studio, TV Tokyo Music, Studio Jack - http://cdn.myanimelist.net/images/anime/7/29246.jpg - Inazuma Eleven Go - - - 6 - 17010 - - http://cdn.myanimelist.net/images/anime/2/45753.jpg - Yasashii March: The Tender March - - - 3 - 9760 - Media Factory, Movic, CoMix Wave, Sentai Filmworks, Yahoo! Japan, Warner Music Japan - http://cdn.myanimelist.net/images/anime/4/29684.jpg - Hoshi wo Ou Kodomo - - - 4 - 9366 - J.C. Staff - http://cdn.myanimelist.net/images/anime/3/29438.jpg - Kaichou wa Maid-sama!: Omake dayo! - - - 3 - 10602 - - http://cdn.myanimelist.net/images/anime/2/29116.jpg - Hayabusa: Back to the Earth - Kikan - - - 3 - 10688 - - http://cdn.myanimelist.net/images/anime/13/29443.jpg - Muybridge's Strings - - - 5 - 10711 - TYO Animations - http://cdn.myanimelist.net/images/anime/13/47551.jpg - Plastic Nee-san - - - 6 - 10720 - - http://cdn.myanimelist.net/images/anime/2/29533.jpg - Korekuraide Utau - - - 5 - 10736 - Toei Animation - http://cdn.myanimelist.net/images/anime/10/29572.jpg - Precure kara Minna e no Ouen Movie - - - 5 - 16602 - - http://cdn.myanimelist.net/images/anime/2/44744.jpg - Dead Girl Trailer - - - 2 - 10191 - Shaft - http://cdn.myanimelist.net/images/anime/3/28416.jpg - Katte ni Kaizou - - - 4 - 10083 - Aniplex, Daume, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/3/36543.jpg - Shiki Specials - - - 4 - 12991 - Shaft - http://cdn.myanimelist.net/images/anime/11/36477.jpg - Katte ni Kaizou Specials - - - 4 - 10737 - Aniplex, Sentai Filmworks, Studio Gokumi - http://cdn.myanimelist.net/images/anime/9/29573.jpg - A-Channel: +A-Channel - - - 4 - 11099 - Sunrise - http://cdn.myanimelist.net/images/anime/4/30751.jpg - Tiger & Bunny Pilot - - - 6 - 10691 - Studio Deen - http://cdn.myanimelist.net/images/anime/4/29446.jpg - Downloader - - - 3 - 7014 - Tezuka Productions - http://cdn.myanimelist.net/images/anime/4/39437.jpg - Tezuka Osamu no Buddha: Akai Sabaku yo! Utsukushiku - - - 2 - 10703 - TMS Entertainment - http://cdn.myanimelist.net/images/anime/11/29489.jpg - Detective Conan OVA 11: A Secret Order from London - + + + + Spring 2011 + 1380217365 + + + 1 + 7081 + 4860 + TV Tokyo, Oriental Light and Magic, Dentsu, Dentsu Entertainment USA + http://cdn.myanimelist.net/images/anime/10/32037.jpg + Danball Senki + + + 2 + 9935 + 5948 + SynergySP + http://cdn.myanimelist.net/images/anime/6/35601.jpg + Chocolat no Mahou + + + 3 + 10500 + 6208 + Telecom Animation Film + http://cdn.myanimelist.net/images/anime/5/29065.jpg + Ojii-san no Lamp + + + 3 + 10501 + 6209 + P.A. Works + http://cdn.myanimelist.net/images/anime/12/29028.jpg + Bannou Yasai Ninninman + + + 3 + 10502 + 6210 + Production I.G + http://cdn.myanimelist.net/images/anime/5/28893.jpg + Tansu Warashi. + + + 3 + 10016 + 5996 + Ascension + http://cdn.myanimelist.net/images/anime/11/29055.jpg + Kizuna Ichigeki + + + 3 + 10534 + 6229 + TV Asahi, Asatsu DK, Shin-Ei Animation + http://cdn.myanimelist.net/images/anime/5/28944.jpg + Doraemon: Nobita and the New Steel Troops - Angel Wings + + + 4 + 10076 + 6023 + J.C. Staff, Nomad, Lantis, DAX Production, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/2/29811.jpg + Kämpfer für die Liebe + + + 5 + 10448 + 6186 + + http://cdn.myanimelist.net/images/anime/5/29062.jpg + Rain Town + + + 5 + 16718 + 7497 + Tatsunoko Productions + http://cdn.myanimelist.net/images/anime/5/44956.jpg + SKET Dance: SD Character Flash Anime + + + 3 + 10114 + 6042 + Ajia-Do + http://cdn.myanimelist.net/images/anime/5/34395.jpg + Nintama Rantarou Movie: Ninjutsu Gakuen Zenin Shutsudou! no Dan + + + 2 + 8857 + 5512 + Kyoto Animation, Animation Do + http://cdn.myanimelist.net/images/anime/6/25521.jpg + Nichijou Episode 0 + + + 4 + 10477 + 5803 + DLE + http://cdn.myanimelist.net/images/anime/6/28784.jpg + Haiyoru! Nyaruani: Remember My Love(craft-sensei) Special + + + 4 + 9734 + 5841 + Kyoto Animation, Pony Canyon, Sentai Filmworks, Animation Do + http://cdn.myanimelist.net/images/anime/7/26965.jpg + K-On!!: Keikaku! + + + 2 + 10406 + 6167 + Studio Puyukai + http://cdn.myanimelist.net/images/anime/9/28590.jpg + Spelunker Sensei + + + 3 + 17233 + 7613 + + http://cdn.myanimelist.net/images/anime/13/46023.jpg + The House + + + 3 + 9999 + 5987 + Toei Animation + http://cdn.myanimelist.net/images/anime/4/32455.jpg + One Piece 3D: Mugiwara Chase + + + 3 + 10074 + 6021 + Toei Animation + http://cdn.myanimelist.net/images/anime/6/28914.jpg + Toriko 3D: Kaimaku Gourmet Adventure!! + + + 3 + 9979 + 5977 + Toei Animation + http://cdn.myanimelist.net/images/anime/12/27934.jpg + Precure All Stars Movie DX3: Mirai ni Todoke! Sekai wo Tsunagu Niji-iro no Hana + + + 2 + 10545 + 6234 + AIC + http://cdn.myanimelist.net/images/anime/11/28972.jpg + The Epic Of ZektBach + + + 4 + 10407 + 6168 + + http://cdn.myanimelist.net/images/anime/6/28586.jpg + Shimanchu MiRiKa + + + 2 + 8063 + 5183 + Studio Deen + http://cdn.myanimelist.net/images/anime/11/35863.jpg + Sekaiichi Hatsukoi OVA + + + 3 + 17104 + 7583 + + http://cdn.myanimelist.net/images/anime/7/45720.jpg + Lolling Seutajeu (Movie) + + + 4 + 10497 + 6206 + Studio Deen, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/13/28864.jpg + Hetalia World Series Extra Episodes + + + 2 + 9088 + 5599 + Sentai Filmworks, Anpro + http://cdn.myanimelist.net/images/anime/7/25186.jpg + Saiyuuki Gaiden + + + 4 + 10504 + 6211 + Bones + http://cdn.myanimelist.net/images/anime/11/28897.jpg + Gosick Recap + + + 1 + 10506 + 6212 + Kachidoki Studio + http://cdn.myanimelist.net/images/anime/2/28900.jpg + Shiawase Haitatsu Taneko + + + 2 + 9774 + 5860 + DAX Production, Studio Gram, Seven, Dream Creation + http://cdn.myanimelist.net/images/anime/12/29056.jpg + Morita-san wa Mukuchi + + + 3 + 10090 + 6030 + Production I.G + http://cdn.myanimelist.net/images/anime/4/29259.jpg + Ghost in the Shell: Stand Alone Complex - Solid State Society 3D + + + 3 + 10092 + 6031 + Production I.G, Xebec, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/11/27832.jpg + Break Blade 6: Doukoku no Toride + + + 3 + 10405 + 6166 + Production I.G + http://cdn.myanimelist.net/images/anime/3/33549.jpg + Xi Avant + + + 1 + 13457 + 6970 + Egg + http://cdn.myanimelist.net/images/anime/10/37603.jpg + Tomodachi 8-nin + + + 2 + 9741 + 5845 + AIC Plus+ + http://cdn.myanimelist.net/images/anime/5/27379.jpg + Nana to Kaoru + + + 2 + 10516 + 6217 + + http://cdn.myanimelist.net/images/anime/4/28918.jpg + Anemone + + + 4 + 10172 + 6063 + FUNimation Entertainment, Media Factory + http://cdn.myanimelist.net/images/anime/9/28079.jpg + Freezing Specials + + + 2 + 9924 + 5942 + AIC, The Right Stuf International + http://cdn.myanimelist.net/images/anime/11/27462.jpg + Shukufuku no Campanella (OVA) + + + 5 + 13501 + + + http://cdn.myanimelist.net/images/anime/12/37695.jpg + Cofun Gal no Coffy (ONA) + + + 1 + 6919 + 4786 + Madhouse Studios, Sony Pictures Entertainment, Marvel Entertainment + http://cdn.myanimelist.net/images/anime/8/30014.jpg + X-Men + + + 5 + 10519 + 6218 + Nitroplus + http://cdn.myanimelist.net/images/anime/4/28921.jpg + Mahou Shoujo Sonico★Magica + + + 5 + 10532 + 6227 + A-1 Pictures + http://cdn.myanimelist.net/images/anime/12/28941.jpg + Working'!! Announcement Specials + + + 1 + 10499 + 6207 + TV Tokyo + http://cdn.myanimelist.net/images/anime/10/28937.jpg + Tottoko Hamtaro Dechu + + + 1 + 10524 + 6221 + Shogakukan Productions + http://cdn.myanimelist.net/images/anime/2/28924.jpg + Duel Masters Victory + + + 1 + 10856 + 6386 + Shogakukan Music & Digital Entertainment + http://cdn.myanimelist.net/images/anime/13/29984.jpg + Penguin no Mondai DX? + + + 1 + 10155 + 6057 + Aniplex, Seven Arcs + http://cdn.myanimelist.net/images/anime/7/29363.jpg + Dog Days + + + 1 + 10033 + 6002 + Toei Animation, FUNimation Entertainment, Yomiko Advertising, Fuji TV + http://cdn.myanimelist.net/images/anime/13/30135.jpg + Toriko + + + 1 + 9289 + 5657 + Bandai Visual, Yomiuri Telecasting Corporation, Lantis, P.A. Works, Pony Canyon, NIS America, Inc., Showgate + http://cdn.myanimelist.net/images/anime/3/28967.jpg + Hanasaku Iroha + + + 1 + 10349 + 6140 + Digital Media Lab + http://cdn.myanimelist.net/images/anime/5/45917.jpg + Suzy's Zoo: Daisuki! Witzy + + + 1 + 10370 + 6152 + SynergySP + http://cdn.myanimelist.net/images/anime/11/29013.jpg + Metal Fight Beyblade 4D + + + 1 + 10165 + 6062 + Kyoto Animation, Lantis, Kadokawa Shoten, Movic, Rakuonsha, Bandai Entertainment, Kadokawa Pictures Japan, The Klock Worx, Animation Do + http://cdn.myanimelist.net/images/anime/9/29714.jpg + Nichijou + + + 1 + 10444 + 6183 + Toei Animation + http://cdn.myanimelist.net/images/anime/11/28688.jpg + Digimon Xros Wars: Aku no Death General to Nanatsu no Oukoku + + + 1 + 9941 + 5953 + Sunrise, Viz Media + http://cdn.myanimelist.net/images/anime/13/29466.jpg + Tiger & Bunny + + + 1 + 10014 + 5994 + Wao World + http://cdn.myanimelist.net/images/anime/5/41117.jpg + Shouwa Monogatari + + + 1 + 10533 + 6228 + + http://cdn.myanimelist.net/images/anime/13/28943.jpg + Fujilog + + + 1 + 10348 + 6139 + Jinnis Animation Studios + http://cdn.myanimelist.net/images/anime/4/28791.jpg + Fireball Charming + + + 1 + 9922 + 5941 + Nomad, FUNimation Entertainment, Lantis, Delphi Sound, Marvelous AQL, Studio Jack + http://cdn.myanimelist.net/images/anime/11/32047.jpg + Oretachi ni Tsubasa wa Nai: Under the Innocent Sky. + + + 1 + 9969 + 5971 + Sunrise, TV Tokyo, Aniplex, Dentsu, Trinity Sound, Miracle Robo, Studio Jack + http://cdn.myanimelist.net/images/anime/4/50361.jpg + Gintama' + + + 1 + 10308 + 6123 + TMS Entertainment, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/2/29775.jpg + Sengoku Otome: Momoiro Paradox + + + 1 + 10324 + 6128 + Frontier Works, Gathering + http://cdn.myanimelist.net/images/anime/10/28398.jpg + Tono to Issho: Gantai no Yabou + + + 1 + 10271 + 6109 + Madhouse Studios + http://cdn.myanimelist.net/images/anime/10/30599.jpg + Gyakkyou Burai Kaiji: Hakairoku Hen + + + 1 + 9253 + 5646 + Frontier Works, FUNimation Entertainment, Media Factory, Movic, AT-X, White Fox, Kadokawa Pictures Japan, Nitroplus + http://cdn.myanimelist.net/images/anime/11/41011.jpg + Steins;Gate + + + 1 + 10347 + 6138 + Shogakukan Music & Digital Entertainment + http://cdn.myanimelist.net/images/anime/3/32993.jpg + Happy Kappy + + + 4 + 10483 + 6201 + Production I.G, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/7/28823.jpg + Sengoku Basara Two: Ryuko, Itadaki no Chikai! Atsuki Mirai e Kakeru Tamashii!! + + + 1 + 9996 + 5985 + Bee Train, NHK, Sogo Vision + http://cdn.myanimelist.net/images/anime/5/28799.jpg + Hyouge Mono + + + 1 + 9863 + 5908 + TV Tokyo, Avex Entertainment, Dentsu, Tatsunoko Productions + http://cdn.myanimelist.net/images/anime/3/28669.jpg + SKET Dance + + + 1 + 9624 + 5812 + Gathering + http://cdn.myanimelist.net/images/anime/9/27498.jpg + 30-sai no Hoken Taiiku + + + 1 + 9493 + 5751 + Brains Base, NIS America, Inc., Dynamic Planning + http://cdn.myanimelist.net/images/anime/13/28978.jpg + Dororon Enma-kun Meeramera + + + 1 + 9776 + 5861 + Aniplex, Dentsu, Mainichi Broadcasting, Sentai Filmworks, Studio Gokumi + http://cdn.myanimelist.net/images/anime/5/29687.jpg + A-Channel + + + 1 + 10109 + 6038 + Xebec, Geneon Universal Entertainment, Lantis, AT-X, The Klock Worx, Studio Mausu + http://cdn.myanimelist.net/images/anime/4/29091.jpg + Softenni + + + 1 + 10187 + 6067 + Xebec + http://cdn.myanimelist.net/images/anime/3/28089.jpg + Hen Zemi (TV) + + + 1 + 10216 + 6082 + Production I.G + http://cdn.myanimelist.net/images/anime/12/28788.jpg + Yondemasu yo, Azazel-san. (TV) + + + 1 + 9712 + 5832 + Shaft, Sentai Filmworks, TV Tokyo Music + http://cdn.myanimelist.net/images/anime/2/28757.jpg + Maria†Holic Alive + + + 1 + 10257 + 6101 + Nomad, Tatsunoko Productions + http://cdn.myanimelist.net/images/anime/9/36423.jpg + Pretty Rhythm: Aurora Dream + + + 1 + 10359 + 6147 + TV Tokyo, Studio Comet, Studio Jack + http://cdn.myanimelist.net/images/anime/7/28699.jpg + Jewelpet Sunshine + + + 1 + 9926 + 5944 + Studio Deen, Lantis + http://cdn.myanimelist.net/images/anime/6/29763.jpg + Sekaiichi Hatsukoi + + + 1 + 10346 + 6137 + Toei Animation + http://cdn.myanimelist.net/images/anime/2/28438.jpg + Ring ni Kakero 1: Sekai Taikai Hen + + + 1 + 9736 + 5843 + Diomedea, Kadokawa Shoten, Pony Canyon, DAX Production, The Klock Worx, ASCII Media Works, Astarotte no Omocha! Production Committee + http://cdn.myanimelist.net/images/anime/6/28794.jpg + Astarotte no Omocha! + + + 1 + 10015 + 5995 + TV Tokyo, Marvelous Entertainment, Konami, NAS, Digital Works, 4Kids Entertainment + http://cdn.myanimelist.net/images/anime/4/40295.jpg + Yu-Gi-Oh! Zexal + + + 1 + 10079 + 6025 + Dentsu, Marvelous Entertainment, Dogakobo, Pony Canyon, feng, PRA, Jumondo + http://cdn.myanimelist.net/images/anime/11/29810.jpg + Hoshizora e Kakaru Hashi + + + 1 + 10073 + 6020 + Hoods Entertainment, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/8/28815.jpg + Seikon no Qwaser II + + + 1 + 10080 + 6026 + TV Tokyo, Geneon Universal Entertainment, Manglobe, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/11/30030.jpg + Kami nomi zo Shiru Sekai II + + + 1 + 10539 + 6231 + DLE + http://cdn.myanimelist.net/images/anime/10/28949.jpg + Hato no Oyomesan + + + 1 + 10540 + 6232 + DLE + http://cdn.myanimelist.net/images/anime/6/28950.jpg + Genki!! Ekoda-chan + + + 1 + 10541 + 6233 + DLE + http://cdn.myanimelist.net/images/anime/4/28951.jpg + Shuukan Shimakou + + + 2 + 9793 + 5871 + A-1 Pictures, Bridge + http://cdn.myanimelist.net/images/anime/5/27146.jpg + Senjou no Valkyria 3: Tagatame no Juusou + + + 1 + 10338 + 6135 + DLE + http://cdn.myanimelist.net/images/anime/7/29163.jpg + Honto ni Atta! Reibai Sensei + + + 1 + 10633 + 6275 + DLE + http://cdn.myanimelist.net/images/anime/8/29262.jpg + Shiodome Cable TV + + + 1 + 10459 + 6190 + DLE + http://cdn.myanimelist.net/images/anime/9/28722.jpg + Puu-Neko + + + 1 + 9989 + 5981 + Aniplex, Dentsu, A-1 Pictures, Fuji TV, NIS America, Inc., Fuji Pacific Music Publishing + http://cdn.myanimelist.net/images/anime/12/28009.jpg + Ano Hi Mita Hana no Namae wo Bokutachi wa Mada Shiranai. + + + 5 + 10850 + 6382 + Shin-Ei Animation + http://cdn.myanimelist.net/images/anime/13/29970.jpg + Seibu Tetsudou Ekiin Tako-chan + + + 2 + 10119 + 6046 + GoHands + http://cdn.myanimelist.net/images/anime/9/41873.jpg + Seitokai Yakuindomo OVA + + + 1 + 9379 + 5710 + Shaft, Starchild Records, NIS America, Inc. + http://cdn.myanimelist.net/images/anime/11/28715.jpg + Denpa Onna to Seishun Otoko + + + 1 + 10163 + 6061 + FUNimation Entertainment, Tatsunoko Productions, Fuji TV, Jumondo, Sony Music Entertainment, Fuji Pacific Music Publishing + http://cdn.myanimelist.net/images/anime/5/50551.jpg + C: The Money of Soul and Possibility Control + + + 1 + 8630 + 5407 + J.C. Staff, FUNimation Entertainment, TBS + http://cdn.myanimelist.net/images/anime/9/30095.jpg + Hidan no Aria + + + 1 + 8143 + 5213 + Iyasakadou Film + http://cdn.myanimelist.net/images/anime/3/24671.jpg + Sockies: Frontier Quest + + + 2 + 9982 + 5979 + Satelight, A-1 Pictures + http://cdn.myanimelist.net/images/anime/6/28494.jpg + Fairy Tail OVA + + + 2 + 10531 + 6226 + Shogakukan Productions, Tokyo Movie Shinsha, Toho Company + http://cdn.myanimelist.net/images/anime/6/28938.jpg + Detective Conan Magic File 5: Niigata - Tokyo Omiyage Capriccio + + + 3 + 10116 + 6044 + Shin-Ei Animation + http://cdn.myanimelist.net/images/anime/9/27894.jpg + Crayon Shin-chan Movie 19: Arashi wo Yobu Ougon no Spy Daisakusen + + + 3 + 9963 + 5967 + Shogakukan Productions, Tokyo Movie Shinsha, Toho Company + http://cdn.myanimelist.net/images/anime/9/32035.jpg + Detective Conan Movie 15: Quarter of Silence + + + 1 + 9919 + 5940 + Aniplex, Dentsu, A-1 Pictures, Mainichi Broadcasting, Movic, Aniplex of America, Sakura Create + http://cdn.myanimelist.net/images/anime/11/29173.jpg + Ao no Exorcist + + + 1 + 6880 + 4765 + Manglobe, FUNimation Entertainment, AMG MUSIC + http://cdn.myanimelist.net/images/anime/12/29882.jpg + Deadman Wonderland + + + 4 + 10536 + 6230 + Production I.G + http://cdn.myanimelist.net/images/anime/10/28946.jpg + Kimi ni Todoke 2nd Season Specials + + + 6 + 11511 + 6525 + + http://cdn.myanimelist.net/images/anime/4/32107.jpg + Evidence + + + 5 + 10766 + 6341 + TMS Entertainment + http://cdn.myanimelist.net/images/anime/7/29671.jpg + Detective Conan vs. Wooo + + + 1 + 10513 + 6216 + Kanaban Graphics + http://cdn.myanimelist.net/images/anime/7/54015.jpg + Usavich IV + + + 1 + 9693 + 5829 + Production I.G, NHK, NHK Enterprises + http://cdn.myanimelist.net/images/anime/9/29166.jpg + Moshidora + + + 2 + 9515 + 5767 + Madhouse Studios, Sentai Filmworks, H.O.T.D Production Committee, Showgate + http://cdn.myanimelist.net/images/anime/10/54427.jpg + Highschool of the Dead: Drifters of the Dead + + + 6 + 10679 + 6287 + Media Factory + http://cdn.myanimelist.net/images/anime/2/29426.jpg + Maria†Holic: Run Run Riru Ran Ran Rara + + + 4 + 10739 + 6326 + Gainax, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/6/29589.jpg + Panty & Stocking in Sanitarybox + + + 3 + 10261 + 6105 + Lapis, Elevenarts + http://cdn.myanimelist.net/images/anime/10/32533.jpg + Toufu Kozou + + + 3 + 8487 + 5354 + Studio Pierrot, Sony Music Entertainment + http://cdn.myanimelist.net/images/anime/3/28871.jpg + Onigamiden + + + 4 + 9925 + 5943 + AIC, Pony Canyon + http://cdn.myanimelist.net/images/anime/5/27464.jpg + Amagami SS: Imouto + + + 4 + 10775 + 6344 + Tatsunoko Productions + http://cdn.myanimelist.net/images/anime/11/29695.jpg + SKET Dance: Demystifying Special + + + 4 + 9888 + 5925 + Diomedea + http://cdn.myanimelist.net/images/anime/7/27364.jpg + Shinryaku! Ika Musume Specials + + + 1 + 10507 + 6213 + TV Tokyo, Oriental Light and Magic, Dentsu, Half H.P Studio, TV Tokyo Music, Studio Jack + http://cdn.myanimelist.net/images/anime/7/29246.jpg + Inazuma Eleven Go + + + 6 + 17010 + + + http://cdn.myanimelist.net/images/anime/2/45753.jpg + Yasashii March: The Tender March + + + 3 + 9760 + 5855 + Media Factory, Movic, CoMix Wave, Sentai Filmworks, Yahoo! Japan, Warner Music Japan + http://cdn.myanimelist.net/images/anime/4/29684.jpg + Hoshi wo Ou Kodomo + + + 4 + 9366 + 5705 + J.C. Staff + http://cdn.myanimelist.net/images/anime/3/29438.jpg + Kaichou wa Maid-sama!: Omake dayo! + + + 3 + 10602 + 6261 + + http://cdn.myanimelist.net/images/anime/2/29116.jpg + Hayabusa: Back to the Earth - Kikan + + + 3 + 10688 + 6294 + + http://cdn.myanimelist.net/images/anime/13/29443.jpg + Muybridge's Strings + + + 5 + 10711 + 6309 + TYO Animations + http://cdn.myanimelist.net/images/anime/13/47551.jpg + Plastic Nee-san + + + 6 + 10720 + 6317 + + http://cdn.myanimelist.net/images/anime/2/29533.jpg + Korekuraide Utau + + + 5 + 10736 + 6324 + Toei Animation + http://cdn.myanimelist.net/images/anime/10/29572.jpg + Precure kara Minna e no Ouen Movie + + + 5 + 16602 + 7470 + + http://cdn.myanimelist.net/images/anime/2/44744.jpg + Dead Girl Trailer + + + 2 + 10191 + 6069 + Shaft + http://cdn.myanimelist.net/images/anime/3/28416.jpg + Katte ni Kaizou + + + 4 + 10083 + 6027 + Aniplex, Daume, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/3/36543.jpg + Shiki Specials + + + 4 + 12991 + 6863 + Shaft + http://cdn.myanimelist.net/images/anime/11/36477.jpg + Katte ni Kaizou Specials + + + 4 + 10737 + 6325 + Aniplex, Sentai Filmworks, Studio Gokumi + http://cdn.myanimelist.net/images/anime/9/29573.jpg + A-Channel: +A-Channel + + + 4 + 11099 + 6459 + Sunrise + http://cdn.myanimelist.net/images/anime/4/30751.jpg + Tiger & Bunny Pilot + + + 6 + 10691 + 6297 + Studio Deen + http://cdn.myanimelist.net/images/anime/4/29446.jpg + Downloader + + + 3 + 7014 + 4831 + Tezuka Productions + http://cdn.myanimelist.net/images/anime/4/39437.jpg + Tezuka Osamu no Buddha: Akai Sabaku yo! Utsukushiku + + + 2 + 10703 + 6304 + TMS Entertainment + http://cdn.myanimelist.net/images/anime/11/29489.jpg + Detective Conan OVA 11: A Secret Order from London + \ No newline at end of file diff --git a/data/db/season/2011_summer.xml b/data/db/season/2011_summer.xml index 3aa877fb8..aed487624 100644 --- a/data/db/season/2011_summer.xml +++ b/data/db/season/2011_summer.xml @@ -1,742 +1,847 @@ - - - - Summer 2011 - 1380252007 - - - 6 - 10799 - Studio Deen - http://cdn.myanimelist.net/images/anime/3/29818.jpg - Chime - - - 2 - 9737 - Production I.G, FUNimation Entertainment, Jinnis Animation Studios - http://cdn.myanimelist.net/images/anime/5/28796.jpg - Appleseed XIII - - - 3 - 9745 - Production I.G, Dentsu, FUNimation Entertainment, Shochiku, Mainichi Broadcasting, Movic, Capcom, flying DOG - http://cdn.myanimelist.net/images/anime/13/50871.jpg - Sengoku Basara Movie: The Last Party - - - 3 - 10710 - Gainax - http://cdn.myanimelist.net/images/anime/11/29512.jpg - Houkago no Pleiades: Manner Movie - - - 3 - 12267 - Studio Ghibli - http://cdn.myanimelist.net/images/anime/12/34539.jpg - Takara-sagashi - - - 5 - 11789 - Sanrio - http://cdn.myanimelist.net/images/anime/13/32955.jpg - Wish Me Mell - - - 3 - 10629 - Madhouse Studios - http://cdn.myanimelist.net/images/anime/6/29247.jpg - Tibet Inu Monogatari - - - 1 - 10797 - - http://cdn.myanimelist.net/images/anime/13/29817.jpg - Kayoe! Chuugaku - - - 1 - 10847 - Gathering, indigo line - http://cdn.myanimelist.net/images/anime/12/36871.jpg - Inumarudashi - - - 2 - 10209 - Studio Deen, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/12/36243.jpg - Kore wa Zombie Desu ka? OVA - - - 2 - 9117 - AIC - http://cdn.myanimelist.net/images/anime/8/29939.jpg - Amagami SS OVA - - - 3 - 10689 - Production I.G, Jinnis Animation Studios - http://cdn.myanimelist.net/images/anime/3/37087.jpg - Appleseed XIII Remix Movie 1: Yuigon - - - 2 - 9618 - FUNimation Entertainment, AIC Plus+ - http://cdn.myanimelist.net/images/anime/7/26653.jpg - Asobi ni Iku yo! Asobi ni Oide - - - 4 - 10842 - Bones - http://cdn.myanimelist.net/images/anime/9/29928.jpg - Fullmetal Alchemist: Milos no Seinaru Hoshi Specials - - - 3 - 10723 - Studio MWP - http://cdn.myanimelist.net/images/anime/5/29569.jpg - Green Days - - - 3 - 10294 - Bones, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/11/35689.jpg - Towa no Quon 1: Utakata no Kaben - - - 4 - 10796 - Xebec - http://cdn.myanimelist.net/images/anime/11/29804.jpg - Softenni Specials - - - 4 - 10647 - Aniplex, A-1 Pictures, Aniplex of America - http://cdn.myanimelist.net/images/anime/11/38149.jpg - Ao no Exorcist Specials - - - 5 - 10859 - Asahi Production - http://cdn.myanimelist.net/images/anime/12/29998.jpg - Sorette Dakara ne! - - - 2 - 10547 - Nomad, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/7/28982.jpg - Oretachi ni Tsubasa wa Nai: Hadairo Ritsu Kyuuwari Zou!? - - - 4 - 10807 - Kyoto Animation - http://cdn.myanimelist.net/images/anime/2/29848.jpg - Nichijou: Tanken Nichijou no Machi - - - 2 - 11699 - indigo line - http://cdn.myanimelist.net/images/anime/7/32647.jpg - Yurumates ha? - - - 3 - 9790 - AIC A.S.T.A., FUNimation Entertainment, NTT Docomo - http://cdn.myanimelist.net/images/anime/8/28729.jpg - Sora no Otoshimono: Tokeijikake no Angeloid - - - 1 - 11589 - - http://cdn.myanimelist.net/images/anime/13/32327.jpg - Puu-Neko Shougekijou - - - 1 - 10838 - DLE - http://cdn.myanimelist.net/images/anime/11/31695.jpg - Double-J - - - 1 - 11609 - DLE - http://cdn.myanimelist.net/images/anime/10/32355.jpg - Shuukan Shimakou: Sono Toki, Shimakou ga Ugoita! - - - 4 - 10479 - Studio Pierrot - http://cdn.myanimelist.net/images/anime/4/31637.jpg - Beelzebub: Hashire! Beel-bo Keiji!! - - - 1 - 6920 - Madhouse Studios, Marvel Entertainment - http://cdn.myanimelist.net/images/anime/6/29742.jpg - Blade - - - 1 - 10802 - DLE - http://cdn.myanimelist.net/images/anime/8/29820.jpg - MonHun Nikki Girigiri Airuu Mura G - - - 1 - 10572 - Barnum Studio, Magic Capsule, Project No.9, Studio Blanc, ASCII Media Works - http://cdn.myanimelist.net/images/anime/11/32643.jpg - Ro-Kyu-Bu! - - - 1 - 10568 - J.C. Staff, Lantis, AT-X, Sentai Filmworks, Warner Bros., The Klock Worx, ASCII Media Works - http://cdn.myanimelist.net/images/anime/3/30180.jpg - Kamisama no Memochou - - - 3 - 10904 - - http://cdn.myanimelist.net/images/anime/13/30098.jpg - Waza no Tabibito - - - 3 - 9135 - Bones, Aniplex, Dentsu, Square Enix, FUNimation Entertainment, Shochiku, Mainichi Broadcasting, TBS - http://cdn.myanimelist.net/images/anime/2/29550.jpg - Fullmetal Alchemist: Milos no Seinaru Hoshi - - - 3 - 10684 - - http://cdn.myanimelist.net/images/anime/10/49479.jpg - Sore Ike! Anpanman: Sukue! Kokorin to Kiseki no Hoshi - - - 3 - 10685 - - http://cdn.myanimelist.net/images/anime/7/29440.jpg - Sore Ike! Anpanman: Utatte Teasobi! Anpanman to Mori no Takara - - - 5 - 10954 - Toei Animation - http://cdn.myanimelist.net/images/anime/7/39885.jpg - Toei Robot Girls - - - 1 - 10995 - - http://cdn.myanimelist.net/images/anime/9/30523.jpg - Ganbare!! Nattou-san - - - 1 - 10049 - Studio Deen, Viz Media - http://cdn.myanimelist.net/images/anime/12/30111.jpg - Nurarihyon no Mago: Sennen Makyou - - - 1 - 10321 - A-1 Pictures, Starchild Records, Movic, Sentai Filmworks, Dwango, Showgate - http://cdn.myanimelist.net/images/anime/6/30248.jpg - Uta no☆Prince-sama♪ Maji Love 1000% - - - 1 - 10156 - Sunrise, Bandai, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/12/30218.jpg - Sacred Seven - - - 5 - 17697 - - http://cdn.myanimelist.net/images/anime/8/47203.jpg - Neon The Animation - - - 1 - 9938 - Satelight, Sentai Filmworks, Sony Music Communications, Bushiroad Inc. - http://cdn.myanimelist.net/images/anime/2/32405.jpg - Ikoku Meiro no Croisée - - - 1 - 10379 - Aniplex, Brains Base, NIS America, Inc., Sakura Create - http://cdn.myanimelist.net/images/anime/7/35323.jpg - Natsume Yuujinchou San - - - 1 - 10217 - J.C. Staff - http://cdn.myanimelist.net/images/anime/11/30254.jpg - Kaitou Tenshi Twin Angel: Kyun Kyun Tokimeki Paradise!! - - - 1 - 10495 - TV Tokyo, Daume, Dogakobo, Pony Canyon, DAX Production, NIS America, Inc., Studio Gram, PRA, Jumondo - http://cdn.myanimelist.net/images/anime/2/52921.jpg - Yuru Yuri - - - 6 - 10849 - Diomedea - http://cdn.myanimelist.net/images/anime/3/29974.jpg - Shinryaku! Ika Musume: Ika Ice Tabena-ika? - - - 1 - 10372 - TV Tokyo, Shogakukan Productions, Media Factory, Brains Base, Half H.P Studio, AT-X, Sentai Filmworks, flying DOG, Sony Music Communications - http://cdn.myanimelist.net/images/anime/4/50393.jpg - Kamisama Dolls - - - 4 - 10920 - TAKI Corporation, Hoods Entertainment - http://cdn.myanimelist.net/images/anime/3/30153.jpg - Seikon no Qwaser II Picture Drama - - - 1 - 10671 - DAX Production, Takeshobo, Seven - http://cdn.myanimelist.net/images/anime/13/30166.jpg - Morita-san wa Mukuchi. - - - 1 - 10197 - Gonzo - http://cdn.myanimelist.net/images/anime/11/46147.jpg - Nyanpire The Animation - - - 1 - 10278 - Aniplex, A-1 Pictures, TBS - http://cdn.myanimelist.net/images/anime/9/41085.jpg - The iDOLM@STER - - - 1 - 8516 - FUNimation Entertainment, Silver Link, Cospa, TV Tokyo Music, PRA - http://cdn.myanimelist.net/images/anime/3/28992.jpg - Baka to Test to Shoukanjuu Ni! - - - 1 - 10110 - Feel, TBS, Sentai Filmworks, PRA - http://cdn.myanimelist.net/images/anime/13/29971.jpg - Mayo Chiki! - - - 1 - 10161 - Bones, Aniplex, Fuji TV, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/2/29759.jpg - No.6 - - - 1 - 10162 - Production I.G, Fuji TV, NIS America, Inc., Sony Music Entertainment, Sakura Create, Fuji Pacific Music Publishing - http://cdn.myanimelist.net/images/anime/3/28014.jpg - Usagi Drop - - - 1 - 10721 - Brains Base, Mainichi Broadcasting, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/5/30238.jpg - Mawaru Penguindrum - - - 1 - 10490 - Production I.G, Aniplex, FUNimation Entertainment, Mainichi Broadcasting, Dwango - http://cdn.myanimelist.net/images/anime/2/31649.jpg - Blood-C - - - 1 - 9750 - Frontier Works, Zexcs - http://cdn.myanimelist.net/images/anime/5/29160.jpg - Itsuka Tenma no Kuro Usagi - - - 1 - 9934 - AT-X, AIC Plus+, NIS America, Inc., Marvelous AQL - http://cdn.myanimelist.net/images/anime/12/27540.jpg - Nekogami Yaoyorozu - - - 1 - 10611 - AIC, Remic, Kadokawa Shoten, Kadokawa Pictures Japan, The Klock Worx, AMG MUSIC, Studio Kelmadick - http://cdn.myanimelist.net/images/anime/9/33029.jpg - R-15 - - - 6 - 10941 - - http://cdn.myanimelist.net/images/anime/12/30186.jpg - Mushi no Tameiki - - - 1 - 10465 - Hoods Entertainment - http://cdn.myanimelist.net/images/anime/4/29688.jpg - Manyuu Hikenchou - - - 4 - 10219 - Zexcs - http://cdn.myanimelist.net/images/anime/11/50539.jpg - Onii-chan no Koto nanka Zenzen Suki Janain Dakara ne!! Special - - - 4 - 13499 - - http://cdn.myanimelist.net/images/anime/11/37693.jpg - Himitsukessha Taka no Tsume THE PLANETARIUM: Burabura! Black Hole no Nazo - - - 3 - 10029 - Studio Ghibli, GKids - http://cdn.myanimelist.net/images/anime/8/32547.jpg - Kokurikozaka kara - - - 3 - 9917 - Production I.G, Xebec, Oriental Light and Magic, The Pokemon Company International - http://cdn.myanimelist.net/images/anime/13/29620.jpg - Pokemon Best Wishes!: Victini to Kuroki Eiyuu Zekrom - - - 1 - 8915 - Gainax, TV Tokyo, Kadokawa Shoten, Kadokawa Pictures Japan, The Klock Worx, NTT Docomo, Dwango - http://cdn.myanimelist.net/images/anime/2/52683.jpg - Dantalian no Shoka - - - 3 - 10713 - Bones, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/8/31827.jpg - Towa no Quon 2: Konton no Ranbu - - - 3 - 10740 - TV Tokyo, Oriental Light and Magic, Shogakukan Productions, Marvelous Entertainment, Toho Company, The Pokemon Company International - http://cdn.myanimelist.net/images/anime/9/29621.jpg - Pokemon Best Wishes!: Victini to Shiroki Eiyuu Reshiram - - - 4 - 11043 - Asahi Production - http://cdn.myanimelist.net/images/anime/5/30669.jpg - Install Pilot - - - 6 - 11543 - - http://cdn.myanimelist.net/images/anime/4/34107.jpg - Hate You - - - 2 - 10196 - Studio Comet - http://cdn.myanimelist.net/images/anime/5/28122.jpg - Baby Princess 3D Paradise 0 [Love] - - - 2 - 10491 - Studio Deen, Frontier Works - http://cdn.myanimelist.net/images/anime/6/29774.jpg - Higurashi no Naku Koro ni Kira - - - 4 - 12131 - - http://cdn.myanimelist.net/images/anime/7/34243.jpg - 30-sai no Hoken Taiiku Specials - - - 4 - 11911 - Nomad, Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/4/33291.jpg - Pretty Rhythm: Aurora Dream Specials - - - 4 - 10950 - Production I.G - http://cdn.myanimelist.net/images/anime/12/30206.jpg - Ghost in the Shell: Stand Alone Complex - Solid State Society 3D - Tachikoma no Hibi - - - 3 - 10702 - Bandai Entertainment, Digital Frontier - http://cdn.myanimelist.net/images/anime/10/50483.jpg - Tekken: Blood Vengeance - - - 4 - 11077 - Madhouse Studios, Satelight, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/5/30667.jpg - Hellsing: The Dawn - - - 6 - 12661 - U/M/A/A Inc. - http://cdn.myanimelist.net/images/anime/10/35519.jpg - Light Lag - - - 4 - 10945 - Shaft - http://cdn.myanimelist.net/images/anime/12/30201.jpg - Maria†Holic Alive Special - - - 3 - 12917 - - http://cdn.myanimelist.net/images/anime/10/36269.jpg - Leafie, A Hen into the Wild - - - 4 - 10804 - Satelight - http://cdn.myanimelist.net/images/anime/8/29825.jpg - Ikoku Meiro no Croisée Special - - - 3 - 10589 - Studio Pierrot, TV Tokyo, Aniplex, Dentsu - http://cdn.myanimelist.net/images/anime/13/41403.jpg - Naruto: Shippuuden Movie 5 - Blood Prison - - - 3 - 4713 - Asahi Production - http://cdn.myanimelist.net/images/anime/10/28748.jpg - Heart no Kuni no Alice: Wonderful Wonder World - - - 3 - 10686 - Studio Pierrot - http://cdn.myanimelist.net/images/anime/9/30813.jpg - Naruto: Honoo no Chuunin Shiken! Naruto vs. Konohamaru!! - - - 4 - 11853 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/7/45528.jpg - Pokemon: Pikachu no Summer Bridge Story - - - 4 - 11359 - Toei Animation, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/13/33877.jpg - Dragon Ball Kai: Mirai ni Heiwa wo! Goku no Tamashii yo Towa ni - - - 2 - 10350 - Studio Deen, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/9/33845.jpg - Hakuouki Sekkaroku - - - 5 - 10801 - Gonzo, Studio Mausu, Slowcurve - http://cdn.myanimelist.net/images/anime/9/30819.jpg - Copihan - - - 3 - 10714 - Bones, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/7/32945.jpg - Towa no Quon 3: Mugen no Renza - - - 6 - 11487 - Studio Deen - http://cdn.myanimelist.net/images/anime/11/32095.jpg - Sekiranun Graffiti - - - 2 - 10012 - Lerche, Notes - http://cdn.myanimelist.net/images/anime/9/28406.jpg - Carnival Phantasm - - - 6 - 15423 - - http://cdn.myanimelist.net/images/anime/9/41871.jpg - Antinotice - - - 4 - 11389 - Shogakukan Music & Digital Entertainment - http://cdn.myanimelist.net/images/anime/13/31771.jpg - Happy Kappy Recap - - - 4 - 13309 - Kyoto Animation, Lantis, Animation Do - http://cdn.myanimelist.net/images/anime/9/47501.jpg - Kyoto Animation: Hoshi-hen - - - 6 - 11033 - Sunrise - http://cdn.myanimelist.net/images/anime/8/30959.jpg - Natsuiro Egao de 1, 2, Jump! - - - 6 - 11189 - - http://cdn.myanimelist.net/images/anime/5/30955.jpg - Pair - - - 4 - 10935 - Aniplex, AIC - http://cdn.myanimelist.net/images/anime/7/30177.jpg - Hourou Musuko Specials - - - 4 - 10923 - J.C. Staff - http://cdn.myanimelist.net/images/anime/13/33559.jpg - Tantei Opera Milky Holmes: Summer Special - - - 4 - 13073 - Production I.G, M.S.C - http://cdn.myanimelist.net/images/anime/12/36739.jpg - Prince of Tennis: Another Story II - Ano Toki no Bokura OVA Bonus - - - 2 - 10573 - M.S.C - http://cdn.myanimelist.net/images/anime/8/29026.jpg - Prince of Tennis: Another Story II - Ano Toki no Bokura - - - 2 - 10595 - Nomad - http://cdn.myanimelist.net/images/anime/10/29481.jpg - VitaminX Addiction - - - 1 - 10908 - - http://cdn.myanimelist.net/images/anime/6/30110.jpg - Kakko Kawaii Sengen! 2 - - - 3 - 7135 - Shaft, Studio Pastoral - http://cdn.myanimelist.net/images/anime/6/29541.jpg - Mahou Sensei Negima! Anime Final - - - 3 - 9958 - TV Tokyo, Geneon Universal Entertainment, Manglobe, Shogakukan Productions, Half H.P Studio, Shogakukan Music & Digital Entertainment - http://cdn.myanimelist.net/images/anime/13/29542.jpg - Hayate no Gotoku! Heaven Is a Place on Earth - + + + + Summer 2011 + 1380252007 + + + 6 + 10799 + 6354 + Studio Deen + http://cdn.myanimelist.net/images/anime/3/29818.jpg + Chime + + + 2 + 9737 + 5844 + Production I.G, FUNimation Entertainment, Jinnis Animation Studios + http://cdn.myanimelist.net/images/anime/5/28796.jpg + Appleseed XIII + + + 3 + 9745 + 5847 + Production I.G, Dentsu, FUNimation Entertainment, Shochiku, Mainichi Broadcasting, Movic, Capcom, flying DOG + http://cdn.myanimelist.net/images/anime/13/50871.jpg + Sengoku Basara Movie: The Last Party + + + 3 + 10710 + 6308 + Gainax + http://cdn.myanimelist.net/images/anime/11/29512.jpg + Houkago no Pleiades: Manner Movie + + + 3 + 12267 + 6697 + Studio Ghibli + http://cdn.myanimelist.net/images/anime/12/34539.jpg + Takara-sagashi + + + 5 + 11789 + 6601 + Sanrio + http://cdn.myanimelist.net/images/anime/13/32955.jpg + Wish Me Mell + + + 3 + 10629 + 6273 + Madhouse Studios + http://cdn.myanimelist.net/images/anime/6/29247.jpg + Tibet Inu Monogatari + + + 1 + 10797 + 6352 + + http://cdn.myanimelist.net/images/anime/13/29817.jpg + Kayoe! Chuugaku + + + 1 + 10847 + 6380 + Gathering, indigo line + http://cdn.myanimelist.net/images/anime/12/36871.jpg + Inumarudashi + + + 2 + 10209 + 6079 + Studio Deen, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/12/36243.jpg + Kore wa Zombie Desu ka? OVA + + + 2 + 9117 + 5606 + AIC + http://cdn.myanimelist.net/images/anime/8/29939.jpg + Amagami SS OVA + + + 3 + 10689 + 6295 + Production I.G, Jinnis Animation Studios + http://cdn.myanimelist.net/images/anime/3/37087.jpg + Appleseed XIII Remix Movie 1: Yuigon + + + 2 + 9618 + 5811 + FUNimation Entertainment, AIC Plus+ + http://cdn.myanimelist.net/images/anime/7/26653.jpg + Asobi ni Iku yo! Asobi ni Oide + + + 4 + 10842 + 6378 + Bones + http://cdn.myanimelist.net/images/anime/9/29928.jpg + Fullmetal Alchemist: Milos no Seinaru Hoshi Specials + + + 3 + 10723 + 6319 + Studio MWP + http://cdn.myanimelist.net/images/anime/5/29569.jpg + Green Days + + + 3 + 10294 + 6116 + Bones, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/11/35689.jpg + Towa no Quon 1: Utakata no Kaben + + + 4 + 10796 + 6351 + Xebec + http://cdn.myanimelist.net/images/anime/11/29804.jpg + Softenni Specials + + + 4 + 10647 + 6279 + Aniplex, A-1 Pictures, Aniplex of America + http://cdn.myanimelist.net/images/anime/11/38149.jpg + Ao no Exorcist Specials + + + 5 + 10859 + 6387 + Asahi Production + http://cdn.myanimelist.net/images/anime/12/29998.jpg + Sorette Dakara ne! + + + 2 + 10547 + 6236 + Nomad, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/7/28982.jpg + Oretachi ni Tsubasa wa Nai: Hadairo Ritsu Kyuuwari Zou!? + + + 4 + 10807 + 6360 + Kyoto Animation + http://cdn.myanimelist.net/images/anime/2/29848.jpg + Nichijou: Tanken Nichijou no Machi + + + 2 + 11699 + 6568 + indigo line + http://cdn.myanimelist.net/images/anime/7/32647.jpg + Yurumates ha? + + + 3 + 9790 + 5869 + AIC A.S.T.A., FUNimation Entertainment, NTT Docomo + http://cdn.myanimelist.net/images/anime/8/28729.jpg + Sora no Otoshimono: Tokeijikake no Angeloid + + + 1 + 11589 + 6543 + + http://cdn.myanimelist.net/images/anime/13/32327.jpg + Puu-Neko Shougekijou + + + 1 + 10838 + 6377 + DLE + http://cdn.myanimelist.net/images/anime/11/31695.jpg + Double-J + + + 1 + 11609 + 6547 + DLE + http://cdn.myanimelist.net/images/anime/10/32355.jpg + Shuukan Shimakou: Sono Toki, Shimakou ga Ugoita! + + + 4 + 10479 + 6199 + Studio Pierrot + http://cdn.myanimelist.net/images/anime/4/31637.jpg + Beelzebub: Hashire! Beel-bo Keiji!! + + + 1 + 6920 + 4787 + Madhouse Studios, Marvel Entertainment + http://cdn.myanimelist.net/images/anime/6/29742.jpg + Blade + + + 1 + 10802 + 6357 + DLE + http://cdn.myanimelist.net/images/anime/8/29820.jpg + MonHun Nikki Girigiri Airuu Mura G + + + 1 + 10572 + 6246 + Barnum Studio, Magic Capsule, Project No.9, Studio Blanc, ASCII Media Works + http://cdn.myanimelist.net/images/anime/11/32643.jpg + Ro-Kyu-Bu! + + + 1 + 10568 + 6244 + J.C. Staff, Lantis, AT-X, Sentai Filmworks, Warner Bros., The Klock Worx, ASCII Media Works + http://cdn.myanimelist.net/images/anime/3/30180.jpg + Kamisama no Memochou + + + 3 + 10904 + 6399 + + http://cdn.myanimelist.net/images/anime/13/30098.jpg + Waza no Tabibito + + + 3 + 9135 + 5613 + Bones, Aniplex, Dentsu, Square Enix, FUNimation Entertainment, Shochiku, Mainichi Broadcasting, TBS + http://cdn.myanimelist.net/images/anime/2/29550.jpg + Fullmetal Alchemist: Milos no Seinaru Hoshi + + + 3 + 10684 + 6290 + + http://cdn.myanimelist.net/images/anime/10/49479.jpg + Sore Ike! Anpanman: Sukue! Kokorin to Kiseki no Hoshi + + + 3 + 10685 + 6291 + + http://cdn.myanimelist.net/images/anime/7/29440.jpg + Sore Ike! Anpanman: Utatte Teasobi! Anpanman to Mori no Takara + + + 5 + 10954 + 6425 + Toei Animation + http://cdn.myanimelist.net/images/anime/5/56489.jpg + Toei Robot Girls + + + 1 + 10995 + 6433 + + http://cdn.myanimelist.net/images/anime/9/30523.jpg + Ganbare!! Nattou-san + + + 1 + 10049 + 7728 + Studio Deen, Viz Media + http://cdn.myanimelist.net/images/anime/12/30111.jpg + Nurarihyon no Mago: Sennen Makyou + + + 1 + 10321 + 6126 + A-1 Pictures, Starchild Records, Movic, Sentai Filmworks, Dwango, Showgate + http://cdn.myanimelist.net/images/anime/6/30248.jpg + Uta no☆Prince-sama♪ Maji Love 1000% + + + 1 + 10156 + 6058 + Sunrise, Bandai, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/12/30218.jpg + Sacred Seven + + + 5 + 17697 + + + http://cdn.myanimelist.net/images/anime/8/47203.jpg + Neon The Animation + + + 1 + 9938 + 5950 + Satelight, Sentai Filmworks, Sony Music Communications, Bushiroad Inc. + http://cdn.myanimelist.net/images/anime/2/32405.jpg + Ikoku Meiro no Croisée + + + 1 + 10379 + 6157 + Aniplex, Brains Base, NIS America, Inc., Sakura Create + http://cdn.myanimelist.net/images/anime/7/35323.jpg + Natsume Yuujinchou San + + + 1 + 10217 + 6083 + J.C. Staff + http://cdn.myanimelist.net/images/anime/11/30254.jpg + Kaitou Tenshi Twin Angel: Kyun Kyun Tokimeki Paradise!! + + + 1 + 10495 + 6205 + TV Tokyo, Daume, Dogakobo, Pony Canyon, DAX Production, NIS America, Inc., Studio Gram, PRA, Jumondo + http://cdn.myanimelist.net/images/anime/2/52921.jpg + Yuru Yuri + + + 6 + 10849 + 6381 + Diomedea + http://cdn.myanimelist.net/images/anime/3/29974.jpg + Shinryaku! Ika Musume: Ika Ice Tabena-ika? + + + 1 + 10372 + 6153 + TV Tokyo, Shogakukan Productions, Media Factory, Brains Base, Half H.P Studio, AT-X, Sentai Filmworks, flying DOG, Sony Music Communications + http://cdn.myanimelist.net/images/anime/4/50393.jpg + Kamisama Dolls + + + 4 + 10920 + 6407 + TAKI Corporation, Hoods Entertainment + http://cdn.myanimelist.net/images/anime/3/30153.jpg + Seikon no Qwaser II Picture Drama + + + 1 + 10671 + 6285 + DAX Production, Takeshobo, Seven + http://cdn.myanimelist.net/images/anime/13/30166.jpg + Morita-san wa Mukuchi. + + + 1 + 10197 + 6072 + Gonzo + http://cdn.myanimelist.net/images/anime/11/46147.jpg + Nyanpire The Animation + + + 1 + 10278 + 6111 + Aniplex, A-1 Pictures, TBS + http://cdn.myanimelist.net/images/anime/9/41085.jpg + The iDOLM@STER + + + 1 + 8516 + 5367 + FUNimation Entertainment, Silver Link, Cospa, TV Tokyo Music, PRA + http://cdn.myanimelist.net/images/anime/3/28992.jpg + Baka to Test to Shoukanjuu Ni! + + + 1 + 10110 + 6039 + Feel, TBS, Sentai Filmworks, PRA + http://cdn.myanimelist.net/images/anime/13/29971.jpg + Mayo Chiki! + + + 1 + 10161 + 6059 + Bones, Aniplex, Fuji TV, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/2/29759.jpg + No.6 + + + 1 + 10162 + 6060 + Production I.G, Fuji TV, NIS America, Inc., Sony Music Entertainment, Sakura Create, Fuji Pacific Music Publishing + http://cdn.myanimelist.net/images/anime/3/28014.jpg + Usagi Drop + + + 1 + 10721 + 6318 + Brains Base, Mainichi Broadcasting, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/5/30238.jpg + Mawaru Penguindrum + + + 1 + 10490 + 6203 + Production I.G, Aniplex, FUNimation Entertainment, Mainichi Broadcasting, Dwango + http://cdn.myanimelist.net/images/anime/2/31649.jpg + Blood-C + + + 1 + 9750 + 5850 + Frontier Works, Zexcs + http://cdn.myanimelist.net/images/anime/5/29160.jpg + Itsuka Tenma no Kuro Usagi + + + 1 + 9934 + 5947 + AT-X, AIC Plus+, NIS America, Inc., Marvelous AQL + http://cdn.myanimelist.net/images/anime/12/27540.jpg + Nekogami Yaoyorozu + + + 1 + 10611 + 6265 + AIC, Remic, Kadokawa Shoten, Kadokawa Pictures Japan, The Klock Worx, AMG MUSIC, Studio Kelmadick + http://cdn.myanimelist.net/images/anime/9/33029.jpg + R-15 + + + 6 + 10941 + 6417 + + http://cdn.myanimelist.net/images/anime/12/30186.jpg + Mushi no Tameiki + + + 1 + 10465 + 6194 + Hoods Entertainment + http://cdn.myanimelist.net/images/anime/4/29688.jpg + Manyuu Hikenchou + + + 4 + 10219 + 6085 + Zexcs + http://cdn.myanimelist.net/images/anime/11/50539.jpg + Onii-chan no Koto nanka Zenzen Suki Janain Dakara ne!! Special + + + 4 + 13499 + 6978 + + http://cdn.myanimelist.net/images/anime/11/37693.jpg + Himitsukessha Taka no Tsume THE PLANETARIUM: Burabura! Black Hole no Nazo + + + 3 + 10029 + 6000 + Studio Ghibli, GKids + http://cdn.myanimelist.net/images/anime/8/32547.jpg + Kokurikozaka kara + + + 3 + 9917 + 5939 + Production I.G, Xebec, Oriental Light and Magic, The Pokemon Company International + http://cdn.myanimelist.net/images/anime/13/29620.jpg + Pokemon Best Wishes!: Victini to Kuroki Eiyuu Zekrom + + + 1 + 8915 + 5530 + Gainax, TV Tokyo, Kadokawa Shoten, Kadokawa Pictures Japan, The Klock Worx, NTT Docomo, Dwango + http://cdn.myanimelist.net/images/anime/2/52683.jpg + Dantalian no Shoka + + + 3 + 10713 + 6310 + Bones, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/8/31827.jpg + Towa no Quon 2: Konton no Ranbu + + + 3 + 10740 + 6327 + TV Tokyo, Oriental Light and Magic, Shogakukan Productions, Marvelous Entertainment, Toho Company, The Pokemon Company International + http://cdn.myanimelist.net/images/anime/9/29621.jpg + Pokemon Best Wishes!: Victini to Shiroki Eiyuu Reshiram + + + 4 + 11043 + 6445 + Asahi Production + http://cdn.myanimelist.net/images/anime/5/30669.jpg + Install Pilot + + + 6 + 11543 + 6533 + + http://cdn.myanimelist.net/images/anime/4/34107.jpg + Hate You + + + 2 + 10196 + 6071 + Studio Comet + http://cdn.myanimelist.net/images/anime/5/28122.jpg + Baby Princess 3D Paradise 0 [Love] + + + 2 + 10491 + 6204 + Studio Deen, Frontier Works + http://cdn.myanimelist.net/images/anime/6/29774.jpg + Higurashi no Naku Koro ni Kira + + + 4 + 12131 + 6670 + + http://cdn.myanimelist.net/images/anime/4/55889.jpg + 30-sai no Hoken Taiiku Specials + + + 4 + 11911 + 6629 + Nomad, Tatsunoko Productions + http://cdn.myanimelist.net/images/anime/4/33291.jpg + Pretty Rhythm: Aurora Dream Specials + + + 4 + 10950 + 6423 + Production I.G + http://cdn.myanimelist.net/images/anime/12/30206.jpg + Ghost in the Shell: Stand Alone Complex - Solid State Society 3D - Tachikoma no Hibi + + + 3 + 10702 + 6303 + Bandai Entertainment, Digital Frontier + http://cdn.myanimelist.net/images/anime/10/50483.jpg + Tekken: Blood Vengeance + + + 4 + 11077 + 6453 + Madhouse Studios, Satelight, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/5/30667.jpg + Hellsing: The Dawn + + + 6 + 12661 + 6782 + U/M/A/A Inc. + http://cdn.myanimelist.net/images/anime/10/35519.jpg + Light Lag + + + 4 + 10945 + 6419 + Shaft + http://cdn.myanimelist.net/images/anime/12/30201.jpg + Maria†Holic Alive Special + + + 3 + 12917 + 6847 + + http://cdn.myanimelist.net/images/anime/10/36269.jpg + Leafie, A Hen into the Wild + + + 4 + 10804 + 6358 + Satelight + http://cdn.myanimelist.net/images/anime/8/29825.jpg + Ikoku Meiro no Croisée Special + + + 3 + 10589 + 6256 + Studio Pierrot, TV Tokyo, Aniplex, Dentsu + http://cdn.myanimelist.net/images/anime/13/41403.jpg + Naruto: Shippuuden Movie 5 - Blood Prison + + + 3 + 4713 + 3754 + Asahi Production + http://cdn.myanimelist.net/images/anime/10/28748.jpg + Heart no Kuni no Alice: Wonderful Wonder World + + + 3 + 10686 + 6292 + Studio Pierrot + http://cdn.myanimelist.net/images/anime/9/30813.jpg + Naruto: Honoo no Chuunin Shiken! Naruto vs. Konohamaru!! + + + 4 + 11853 + 6617 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/7/45528.jpg + Pokemon: Pikachu no Summer Bridge Story + + + 4 + 11359 + 6500 + Toei Animation, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/13/33877.jpg + Dragon Ball Kai: Mirai ni Heiwa wo! Goku no Tamashii yo Towa ni + + + 2 + 10350 + 6141 + Studio Deen, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/9/33845.jpg + Hakuouki Sekkaroku + + + 5 + 10801 + 6356 + Gonzo, Studio Mausu, Slowcurve + http://cdn.myanimelist.net/images/anime/9/30819.jpg + Copihan + + + 3 + 10714 + 6311 + Bones, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/7/32945.jpg + Towa no Quon 3: Mugen no Renza + + + 6 + 11487 + 6518 + Studio Deen + http://cdn.myanimelist.net/images/anime/11/32095.jpg + Sekiranun Graffiti + + + 2 + 10012 + 5992 + Lerche, Notes + http://cdn.myanimelist.net/images/anime/9/28406.jpg + Carnival Phantasm + + + 6 + 15423 + 7254 + + http://cdn.myanimelist.net/images/anime/9/41871.jpg + Antinotice + + + 4 + 11389 + 6504 + Shogakukan Music & Digital Entertainment + http://cdn.myanimelist.net/images/anime/13/31771.jpg + Happy Kappy Recap + + + 4 + 13309 + 6937 + Kyoto Animation, Lantis, Animation Do + http://cdn.myanimelist.net/images/anime/9/47501.jpg + Kyoto Animation: Hoshi-hen + + + 6 + 11033 + 6444 + Sunrise + http://cdn.myanimelist.net/images/anime/8/30959.jpg + Natsuiro Egao de 1, 2, Jump! + + + 6 + 11189 + 6471 + + http://cdn.myanimelist.net/images/anime/5/30955.jpg + Pair + + + 4 + 10935 + 6413 + Aniplex, AIC + http://cdn.myanimelist.net/images/anime/7/30177.jpg + Hourou Musuko Specials + + + 4 + 10923 + 6408 + J.C. Staff + http://cdn.myanimelist.net/images/anime/13/33559.jpg + Tantei Opera Milky Holmes: Summer Special + + + 4 + 13073 + 6876 + Production I.G, M.S.C + http://cdn.myanimelist.net/images/anime/12/36739.jpg + Prince of Tennis: Another Story II - Ano Toki no Bokura OVA Bonus + + + 2 + 10573 + 6247 + M.S.C + http://cdn.myanimelist.net/images/anime/8/29026.jpg + Prince of Tennis: Another Story II - Ano Toki no Bokura + + + 2 + 10595 + 6260 + Nomad + http://cdn.myanimelist.net/images/anime/10/29481.jpg + VitaminX Addiction + + + 1 + 10908 + 6402 + + http://cdn.myanimelist.net/images/anime/6/30110.jpg + Kakko Kawaii Sengen! 2 + + + 3 + 7135 + 4876 + Shaft, Studio Pastoral + http://cdn.myanimelist.net/images/anime/6/29541.jpg + Mahou Sensei Negima! Anime Final + + + 3 + 9958 + 5965 + TV Tokyo, Geneon Universal Entertainment, Manglobe, Shogakukan Productions, Half H.P Studio, Shogakukan Music & Digital Entertainment + http://cdn.myanimelist.net/images/anime/13/29542.jpg + Hayate no Gotoku! Heaven Is a Place on Earth + \ No newline at end of file diff --git a/data/db/season/2011_winter.xml b/data/db/season/2011_winter.xml index 6cc4fe233..7924afd03 100644 --- a/data/db/season/2011_winter.xml +++ b/data/db/season/2011_winter.xml @@ -1,616 +1,703 @@ - - - - Winter 2011 - 1380038333 - - - 3 - 8247 - Studio Pierrot, Viz Media - http://cdn.myanimelist.net/images/anime/9/26792.jpg - Bleach: Jigokuhen - - - 2 - 8249 - Sunrise - http://cdn.myanimelist.net/images/anime/10/23339.jpg - Votoms Finder - - - 2 - 8460 - Asread - http://cdn.myanimelist.net/images/anime/10/22971.jpg - Mirai Nikki - - - 2 - 9159 - Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/12/27643.jpg - Princess Resurrection (OVA) - - - 1 - 9598 - DLE, SoftBank Creative Corp. - http://cdn.myanimelist.net/images/anime/8/27429.jpg - Haiyoru! Nyaruani: Remember My Love(craft-sensei) - - - 4 - 9735 - Sunrise - http://cdn.myanimelist.net/images/anime/12/26966.jpg - Gintama: Shinyaku Benizakura-hen Special - - - 6 - 13053 - - http://cdn.myanimelist.net/images/anime/7/36659.jpg - Aimai Elegy - - - 4 - 17753 - DAX Production - http://cdn.myanimelist.net/images/anime/2/47305.jpg - Kuruneko: Nyaalock Holmes no Bouken - - - 2 - 9890 - SynergySP - http://cdn.myanimelist.net/images/anime/5/28422.jpg - Major: Message - - - 3 - 9339 - Toho Company - http://cdn.myanimelist.net/images/anime/3/25870.jpg - Kuma no Gakkou: Jackie to Katie - - - 3 - 9340 - - http://cdn.myanimelist.net/images/anime/5/25871.jpg - Cheburashka - - - 2 - 9608 - ufotable - http://cdn.myanimelist.net/images/anime/9/26613.jpg - Yuri Seijin Naoko-san - - - 3 - 12867 - ufotable - http://cdn.myanimelist.net/images/anime/5/36085.jpg - Tsuki no Sango - - - 2 - 10882 - StudioAnimal Co. - http://cdn.myanimelist.net/images/anime/7/30034.jpg - Ghost Messenger - - - 4 - 10067 - Aniplex, Key, P.A. Works, Visual Art’s - http://cdn.myanimelist.net/images/anime/4/27786.jpg - Angel Beats!: Another Epilogue - - - 4 - 9062 - Aniplex, Key, P.A. Works, Visual Art’s, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/4/25073.jpg - Angel Beats!: Stairway to Heaven - - - 4 - 9581 - Xebec - http://cdn.myanimelist.net/images/anime/7/26547.jpg - MM! Specials - - - 4 - 10104 - Production I.G, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/11/27873.jpg - Loups=Garous Picture Drama - - - 4 - 10108 - Aniplex, AIC Build - http://cdn.myanimelist.net/images/anime/8/28061.jpg - Ore no Imouto ga Konnani Kawaii Wake ga Nai Animated Commentary - - - 4 - 9124 - A-1 Pictures - http://cdn.myanimelist.net/images/anime/3/25253.jpg - Ookiku Furikabutte: Natsu no Taikai Hen Special - - - 3 - 12723 - Production I.G, Trans Arts, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/11/35729.jpg - Loups=Garous Pilot - - - 4 - 9673 - Studio Pierrot - http://cdn.myanimelist.net/images/anime/2/26809.jpg - Tegami Bachi Reverse Specials - - - 6 - 9930 - Sunrise - http://cdn.myanimelist.net/images/anime/11/27492.jpg - Snow Halation - - - 4 - 10201 - Aniplex, A-1 Pictures - http://cdn.myanimelist.net/images/anime/7/28136.jpg - Senkou no Night Raid Picture Drama - - - 5 - 7705 - Studio Deen, Frontier Works - http://cdn.myanimelist.net/images/anime/7/27459.jpg - Starry☆Sky - - - 3 - 9032 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/12/31189.jpg - Inazuma Eleven: Saikyou Gundan Ogre Shuurai - - - 2 - 9744 - Primastea - http://cdn.myanimelist.net/images/anime/8/26989.jpg - Issho ni Training Ofuro: Bathtime with Hinako & Hiyoko - - - 2 - 6787 - Lantis - http://cdn.myanimelist.net/images/anime/2/15684.jpg - Goulart Knights: Evoked the Beginning Black - - - 3 - 8098 - Xebec, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/5/31163.jpg - Soukyuu no Fafner: Dead Aggressor - Heaven and Earth - - - 1 - 8425 - Bones, TV Tokyo, Kadokawa Shoten, Bandai Entertainment, The Klock Worx, Memory-Tech, NTT Docomo - http://cdn.myanimelist.net/images/anime/11/27906.jpg - Gosick - - - 2 - 9943 - - http://cdn.myanimelist.net/images/anime/11/27510.jpg - Ore-sama Kingdom - - - 2 - 9944 - - http://cdn.myanimelist.net/images/anime/8/27511.jpg - Hime Gal♥Paradise - - - 2 - 10075 - Studio Pierrot - http://cdn.myanimelist.net/images/anime/3/30485.jpg - Naruto x UT - - - 1 - 8241 - Xebec, Avex Entertainment, Pony Canyon, Media Blasters - http://cdn.myanimelist.net/images/anime/6/27707.jpg - Rio: Rainbow Gate! - - - 5 - 11611 - - http://cdn.myanimelist.net/images/anime/4/32357.jpg - Shuukan Shimakou Special - - - 4 - 10152 - Production I.G - http://cdn.myanimelist.net/images/anime/12/28011.jpg - Kimi ni Todoke 2nd Season: Kataomoi - - - 1 - 6918 - Madhouse Studios, Marvel Entertainment - http://cdn.myanimelist.net/images/anime/11/28039.jpg - Wolverine - - - 1 - 9756 - Aniplex, Shaft, Mainichi Broadcasting, Movic, Nitroplus, Aniplex of America, Madoka Partners, Hobunsha - http://cdn.myanimelist.net/images/anime/8/28483.jpg - Mahou Shoujo Madoka★Magica - - - 1 - 9041 - Lantis, TBS, Sentai Filmworks, 8bit, Sony Music Communications, Project IS, Sakura Create - http://cdn.myanimelist.net/images/anime/10/26111.jpg - IS: Infinite Stratos - - - 1 - 9331 - J.C. Staff, Pony Canyon, TBS, Magic Capsule, Sentai Filmworks, Hobunsha - http://cdn.myanimelist.net/images/anime/3/30869.jpg - Yumekui Merry - - - 4 - 10178 - Bones, NHK, Production Reed, The Answer Studio, Wao World - http://cdn.myanimelist.net/images/anime/11/28531.jpg - Otona Joshi no Anime Time - - - 2 - 8250 - Sunrise - http://cdn.myanimelist.net/images/anime/12/23340.jpg - Soukou Kihei Votoms: Koei Futatabi - - - 1 - 9539 - Dentsu, Sotsu Agency, TMS Entertainment, Sakura Create - http://cdn.myanimelist.net/images/anime/11/41625.jpg - Cardfight!! Vanguard - - - 1 - 9367 - FUNimation Entertainment, Media Factory, A.C.G.T. - http://cdn.myanimelist.net/images/anime/10/28535.jpg - Freezing - - - 1 - 9510 - Lantis, Bridge, Cospa, Studio Mausu - http://cdn.myanimelist.net/images/anime/12/53949.jpg - Mitsudomoe Zouryouchuu! - - - 1 - 9513 - Studio Pierrot, Dentsu - http://cdn.myanimelist.net/images/anime/3/28013.jpg - Beelzebub - - - 1 - 9587 - Starchild Records, Zexcs - http://cdn.myanimelist.net/images/anime/3/27721.jpg - Onii-chan no Koto nanka Zenzen Suki Janain Dakara ne!! - - - 1 - 9834 - Studio Pierrot, Aniplex, FUNimation Entertainment, David Production, TV Tokyo Music, Sony Music Entertainment, Sakura Create - http://cdn.myanimelist.net/images/anime/4/29668.jpg - Level E - - - 1 - 9330 - Studio Deen, Yomiuri Telecasting Corporation, Starchild Records, Kids Station, DAX Production - http://cdn.myanimelist.net/images/anime/8/50311.jpg - Dragon Crisis! - - - 1 - 8841 - Studio Deen, FUNimation Entertainment, Kadokawa Shoten, AT-X, Kadokawa Pictures Japan, The Klock Worx, flying DOG - http://cdn.myanimelist.net/images/anime/8/29748.jpg - Kore wa Zombie Desu ka? - - - 1 - 9656 - Production I.G, NIS America, Inc. - http://cdn.myanimelist.net/images/anime/5/27993.jpg - Kimi ni Todoke 2nd Season - - - 1 - 10177 - - http://cdn.myanimelist.net/images/anime/12/28072.jpg - DD Hokuto no Ken - - - 1 - 9314 - A-1 Pictures, FUNimation Entertainment, Ordet, Asmik Ace Entertainment, Sony Music Entertainment - http://cdn.myanimelist.net/images/anime/2/28197.jpg - Fractale - - - 1 - 8426 - Aniplex, AIC, Dentsu, Fuji TV, Magic Capsule, Enterbrain - http://cdn.myanimelist.net/images/anime/13/53945.jpg - Hourou Musuko - - - 1 - 11733 - - http://cdn.myanimelist.net/images/anime/13/32755.jpg - Chi-Sui Maru 2nd Season - - - 3 - 18489 - - http://cdn.myanimelist.net/images/anime/7/49467.jpg - Xi Yang Yang Yu Hui Tai Lang: Zhi Tu Nian Ding Gua Gua - - - 5 - 10323 - - http://cdn.myanimelist.net/images/anime/3/28397.jpg - Golden Kids - - - 2 - 9782 - Sunrise, Sentai Filmworks, Showgate - http://cdn.myanimelist.net/images/anime/10/31755.jpg - Norageki! - - - 3 - 9724 - Production I.G, Xebec, Sentai Filmworks, The Klock Worx - http://cdn.myanimelist.net/images/anime/12/26948.jpg - Break Blade 5: Shisen no Hate - - - 3 - 10238 - - http://cdn.myanimelist.net/images/anime/3/28223.jpg - Hybrid Deka - - - 4 - 10249 - J.C. Staff - http://cdn.myanimelist.net/images/anime/2/28233.jpg - Toaru Majutsu no Index II Specials - - - 2 - 9393 - Nomad - http://cdn.myanimelist.net/images/anime/3/36771.jpg - T.P. Sakura: Time Paladin Sakura - - - 2 - 16373 - CoMix Wave - http://cdn.myanimelist.net/images/anime/12/44189.jpg - Peeping Life: The Perfect Evolution - - - 4 - 9754 - FUNimation Entertainment, Brains Base, Asmik Ace Entertainment - http://cdn.myanimelist.net/images/anime/6/27015.jpg - Kuragehime Specials - - - 2 - 9332 - Bandai Visual, FUNimation Entertainment, Kinema Citrus, flying DOG, Showgate, BIGLOBE - http://cdn.myanimelist.net/images/anime/5/28371.jpg - .hack//Quantum - - - 4 - 10390 - Bandai Visual, Kinema Citrus - http://cdn.myanimelist.net/images/anime/4/29050.jpg - .hack//Quantum Specials - - - 6 - 13311 - - http://cdn.myanimelist.net/images/anime/4/37353.jpg - Henshin Gattai! 5 tsu no Atsuki Tamashii - - - 3 - 10013 - Wao World - http://cdn.myanimelist.net/images/anime/8/41119.jpg - Shouwa Monogatari (Movie) - - - 6 - 10445 - Madhouse Studios - http://cdn.myanimelist.net/images/anime/12/29356.jpg - Perfect Day - - - 5 - 9911 - Gainax - http://cdn.myanimelist.net/images/anime/9/47741.jpg - Houkago no Pleiades - - - 4 - 6954 - ufotable, Aniplex of America - http://cdn.myanimelist.net/images/anime/3/52557.jpg - Kara no Kyoukai: Epilogue - - - 2 - 10297 - Dogakobo - http://cdn.myanimelist.net/images/anime/6/28330.jpg - Shin Koihime†Musou: Otome Tairan OVA - - - 4 - 15015 - - http://cdn.myanimelist.net/images/anime/3/40773.jpg - Shin Koihime†Musou: Otome Tairan OVA Omake - - - 4 - 10302 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/11/28340.jpg - Pokemon Diamond & Pearl Special - - - 2 - 10361 - Satelight - http://cdn.myanimelist.net/images/anime/9/28487.jpg - Macross Frontier: Chou Jikuu Gekijou - - - 1 - 9893 - Toei Animation - http://cdn.myanimelist.net/images/anime/4/28333.jpg - Suite Precure♪ - - - 1 - 10330 - TMS Entertainment, Nelvana - http://cdn.myanimelist.net/images/anime/2/49725.jpg - Bakugan Battle Brawlers: Mechtanium Surge - - - 4 - 9940 - P.A. Works - http://cdn.myanimelist.net/images/anime/11/27495.jpg - Mai no Mahou to Katei no Hi - - - 4 - 10020 - Aniplex, AIC Build, Aniplex of America - http://cdn.myanimelist.net/images/anime/8/29734.jpg - Ore no Imouto ga Konnani Kawaii Wake ga Nai Specials - - - 2 - 8986 - Madhouse Studios, Warner Bros. - http://cdn.myanimelist.net/images/anime/8/37135.jpg - Supernatural The Animation - - - 2 - 9347 - Feel, Zexcs - http://cdn.myanimelist.net/images/anime/6/25892.jpg - Fortune Arterial: Akai Yakusoku - Tadoritsuita Basho - - - 2 - 9611 - AIC - http://cdn.myanimelist.net/images/anime/5/28569.jpg - Aa! Megami-sama! (2011) - - - 2 - 9130 - TMS Entertainment - http://cdn.myanimelist.net/images/anime/12/29597.jpg - Saint Seiya: The Lost Canvas - Meiou Shinwa 2 - - - 4 - 9396 - Lantis, Bridge - http://cdn.myanimelist.net/images/anime/8/26045.jpg - Mitsudomoe Special - - - 4 - 10431 - Silver Link - http://cdn.myanimelist.net/images/anime/12/29181.jpg - Baka to Test to Shoukanjuu: Matsuri Special - - - 2 - 9471 - FUNimation Entertainment, Silver Link - http://cdn.myanimelist.net/images/anime/8/27463.jpg - Baka to Test to Shoukanjuu: Matsuri - - - 3 - 7222 - Satelight, 8bit - http://cdn.myanimelist.net/images/anime/5/33137.jpg - Macross Frontier: Sayonara no Tsubasa - + + + + Winter 2011 + 1380038333 + + + 3 + 8247 + 5267 + Studio Pierrot, Viz Media + http://cdn.myanimelist.net/images/anime/9/26792.jpg + Bleach: Jigokuhen + + + 2 + 8249 + 5269 + Sunrise + http://cdn.myanimelist.net/images/anime/10/23339.jpg + Votoms Finder + + + 2 + 8460 + 5344 + Asread + http://cdn.myanimelist.net/images/anime/10/22971.jpg + Mirai Nikki + + + 2 + 9159 + 5616 + Tatsunoko Productions + http://cdn.myanimelist.net/images/anime/12/27643.jpg + Princess Resurrection (OVA) + + + 1 + 9598 + 7808 + DLE, SoftBank Creative Corp. + http://cdn.myanimelist.net/images/anime/8/27429.jpg + Haiyoru! Nyaruani: Remember My Love(craft-sensei) + + + 4 + 9735 + 5842 + Sunrise + http://cdn.myanimelist.net/images/anime/12/26966.jpg + Gintama: Shinyaku Benizakura-hen Special + + + 6 + 13053 + 6872 + + http://cdn.myanimelist.net/images/anime/7/36659.jpg + Aimai Elegy + + + 4 + 17753 + + DAX Production + http://cdn.myanimelist.net/images/anime/2/47305.jpg + Kuruneko: Nyaalock Holmes no Bouken + + + 2 + 9890 + 5926 + SynergySP + http://cdn.myanimelist.net/images/anime/5/28422.jpg + Major: Message + + + 3 + 9339 + 5686 + Toho Company + http://cdn.myanimelist.net/images/anime/3/25870.jpg + Kuma no Gakkou: Jackie to Katie + + + 3 + 9340 + 5687 + + http://cdn.myanimelist.net/images/anime/5/25871.jpg + Cheburashka + + + 2 + 9608 + 5807 + ufotable + http://cdn.myanimelist.net/images/anime/9/26613.jpg + Yuri Seijin Naoko-san + + + 3 + 12867 + 6831 + ufotable + http://cdn.myanimelist.net/images/anime/5/36085.jpg + Tsuki no Sango + + + 2 + 10882 + 6391 + StudioAnimal Co. + http://cdn.myanimelist.net/images/anime/7/30034.jpg + Ghost Messenger + + + 4 + 10067 + 6019 + Aniplex, Key, P.A. Works, Visual Art’s + http://cdn.myanimelist.net/images/anime/4/27786.jpg + Angel Beats!: Another Epilogue + + + 4 + 9062 + 5591 + Aniplex, Key, P.A. Works, Visual Art’s, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/4/25073.jpg + Angel Beats!: Stairway to Heaven + + + 4 + 9581 + 5797 + Xebec + http://cdn.myanimelist.net/images/anime/7/26547.jpg + MM! Specials + + + 4 + 10104 + 6034 + Production I.G, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/11/27873.jpg + Loups=Garous Picture Drama + + + 4 + 10108 + 6037 + Aniplex, AIC Build + http://cdn.myanimelist.net/images/anime/8/28061.jpg + Ore no Imouto ga Konnani Kawaii Wake ga Nai Animated Commentary + + + 4 + 9124 + 5610 + A-1 Pictures + http://cdn.myanimelist.net/images/anime/3/25253.jpg + Ookiku Furikabutte: Natsu no Taikai Hen Special + + + 3 + 12723 + 6798 + Production I.G, Trans Arts, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/11/35729.jpg + Loups=Garous Pilot + + + 4 + 9673 + 5824 + Studio Pierrot + http://cdn.myanimelist.net/images/anime/2/26809.jpg + Tegami Bachi Reverse Specials + + + 6 + 9930 + 5945 + Sunrise + http://cdn.myanimelist.net/images/anime/11/27492.jpg + Snow Halation + + + 4 + 10201 + 6075 + Aniplex, A-1 Pictures + http://cdn.myanimelist.net/images/anime/7/28136.jpg + Senkou no Night Raid Picture Drama + + + 5 + 7705 + 5087 + Studio Deen, Frontier Works + http://cdn.myanimelist.net/images/anime/7/27459.jpg + Starry☆Sky + + + 3 + 9032 + 5580 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/12/31189.jpg + Inazuma Eleven: Saikyou Gundan Ogre Shuurai + + + 2 + 9744 + 5846 + Primastea + http://cdn.myanimelist.net/images/anime/8/26989.jpg + Issho ni Training Ofuro: Bathtime with Hinako & Hiyoko + + + 2 + 6787 + 4714 + Lantis + http://cdn.myanimelist.net/images/anime/2/15684.jpg + Goulart Knights: Evoked the Beginning Black + + + 3 + 8098 + 5194 + Xebec, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/5/31163.jpg + Soukyuu no Fafner: Dead Aggressor - Heaven and Earth + + + 1 + 8425 + 5330 + Bones, TV Tokyo, Kadokawa Shoten, Bandai Entertainment, The Klock Worx, Memory-Tech, NTT Docomo + http://cdn.myanimelist.net/images/anime/11/27906.jpg + Gosick + + + 2 + 9943 + 5954 + + http://cdn.myanimelist.net/images/anime/11/27510.jpg + Ore-sama Kingdom + + + 2 + 9944 + 5955 + + http://cdn.myanimelist.net/images/anime/8/27511.jpg + Hime Gal♥Paradise + + + 2 + 10075 + 6022 + Studio Pierrot + http://cdn.myanimelist.net/images/anime/3/30485.jpg + Naruto x UT + + + 1 + 8241 + 5262 + Xebec, Avex Entertainment, Pony Canyon, Media Blasters + http://cdn.myanimelist.net/images/anime/6/27707.jpg + Rio: Rainbow Gate! + + + 5 + 11611 + 6548 + + http://cdn.myanimelist.net/images/anime/4/32357.jpg + Shuukan Shimakou Special + + + 4 + 10152 + 6055 + Production I.G + http://cdn.myanimelist.net/images/anime/12/28011.jpg + Kimi ni Todoke 2nd Season: Kataomoi + + + 1 + 6918 + 4785 + Madhouse Studios, Marvel Entertainment + http://cdn.myanimelist.net/images/anime/11/28039.jpg + Wolverine + + + 1 + 9756 + 5853 + Aniplex, Shaft, Mainichi Broadcasting, Movic, Nitroplus, Aniplex of America, Madoka Partners, Hobunsha + http://cdn.myanimelist.net/images/anime/8/28483.jpg + Mahou Shoujo Madoka★Magica + + + 1 + 9041 + 5583 + Lantis, TBS, Sentai Filmworks, 8bit, Sony Music Communications, Project IS, Sakura Create + http://cdn.myanimelist.net/images/anime/10/26111.jpg + IS: Infinite Stratos + + + 1 + 9331 + 5679 + J.C. Staff, Pony Canyon, TBS, Magic Capsule, Sentai Filmworks, Hobunsha + http://cdn.myanimelist.net/images/anime/3/30869.jpg + Yumekui Merry + + + 4 + 10178 + 6065 + Bones, NHK, Production Reed, The Answer Studio, Wao World + http://cdn.myanimelist.net/images/anime/11/28531.jpg + Otona Joshi no Anime Time + + + 2 + 8250 + 5270 + Sunrise + http://cdn.myanimelist.net/images/anime/12/23340.jpg + Soukou Kihei Votoms: Koei Futatabi + + + 1 + 9539 + 5781 + Dentsu, Sotsu Agency, TMS Entertainment, Sakura Create + http://cdn.myanimelist.net/images/anime/11/41625.jpg + Cardfight!! Vanguard + + + 1 + 9367 + 5706 + FUNimation Entertainment, Media Factory, A.C.G.T. + http://cdn.myanimelist.net/images/anime/10/28535.jpg + Freezing + + + 1 + 9510 + 5765 + Lantis, Bridge, Cospa, Studio Mausu + http://cdn.myanimelist.net/images/anime/12/53949.jpg + Mitsudomoe Zouryouchuu! + + + 1 + 9513 + 5766 + Studio Pierrot, Dentsu + http://cdn.myanimelist.net/images/anime/3/28013.jpg + Beelzebub + + + 1 + 9587 + 5799 + Starchild Records, Zexcs + http://cdn.myanimelist.net/images/anime/3/27721.jpg + Onii-chan no Koto nanka Zenzen Suki Janain Dakara ne!! + + + 1 + 9834 + 5895 + Studio Pierrot, Aniplex, FUNimation Entertainment, David Production, TV Tokyo Music, Sony Music Entertainment, Sakura Create + http://cdn.myanimelist.net/images/anime/4/29668.jpg + Level E + + + 1 + 9330 + 5678 + Studio Deen, Yomiuri Telecasting Corporation, Starchild Records, Kids Station, DAX Production + http://cdn.myanimelist.net/images/anime/8/50311.jpg + Dragon Crisis! + + + 1 + 8841 + 5507 + Studio Deen, FUNimation Entertainment, Kadokawa Shoten, AT-X, Kadokawa Pictures Japan, The Klock Worx, flying DOG + http://cdn.myanimelist.net/images/anime/8/29748.jpg + Kore wa Zombie Desu ka? + + + 1 + 9656 + 5820 + Production I.G, NIS America, Inc. + http://cdn.myanimelist.net/images/anime/5/27993.jpg + Kimi ni Todoke 2nd Season + + + 1 + 10177 + 6064 + + http://cdn.myanimelist.net/images/anime/12/28072.jpg + DD Hokuto no Ken + + + 1 + 9314 + 5668 + A-1 Pictures, FUNimation Entertainment, Ordet, Asmik Ace Entertainment, Sony Music Entertainment + http://cdn.myanimelist.net/images/anime/2/28197.jpg + Fractale + + + 1 + 8426 + 5331 + Aniplex, AIC, Dentsu, Fuji TV, Magic Capsule, Enterbrain + http://cdn.myanimelist.net/images/anime/13/53945.jpg + Hourou Musuko + + + 1 + 11733 + 6578 + + http://cdn.myanimelist.net/images/anime/13/32755.jpg + Chi-Sui Maru 2nd Season + + + 3 + 18489 + + + http://cdn.myanimelist.net/images/anime/7/49467.jpg + Xi Yang Yang Yu Hui Tai Lang: Zhi Tu Nian Ding Gua Gua + + + 5 + 10323 + 6127 + + http://cdn.myanimelist.net/images/anime/3/28397.jpg + Golden Kids + + + 2 + 9782 + 5865 + Sunrise, Sentai Filmworks, Showgate + http://cdn.myanimelist.net/images/anime/10/31755.jpg + Norageki! + + + 3 + 9724 + 5837 + Production I.G, Xebec, Sentai Filmworks, The Klock Worx + http://cdn.myanimelist.net/images/anime/12/26948.jpg + Break Blade 5: Shisen no Hate + + + 3 + 10238 + 6090 + + http://cdn.myanimelist.net/images/anime/3/28223.jpg + Hybrid Deka + + + 4 + 10249 + 6098 + J.C. Staff + http://cdn.myanimelist.net/images/anime/2/28233.jpg + Toaru Majutsu no Index II Specials + + + 2 + 9393 + 5719 + Nomad + http://cdn.myanimelist.net/images/anime/3/36771.jpg + T.P. Sakura: Time Paladin Sakura + + + 2 + 16373 + 7414 + CoMix Wave + http://cdn.myanimelist.net/images/anime/12/44189.jpg + Peeping Life: The Perfect Evolution + + + 4 + 9754 + 5852 + FUNimation Entertainment, Brains Base, Asmik Ace Entertainment + http://cdn.myanimelist.net/images/anime/6/27015.jpg + Kuragehime Specials + + + 2 + 9332 + 5680 + Bandai Visual, FUNimation Entertainment, Kinema Citrus, flying DOG, Showgate, BIGLOBE + http://cdn.myanimelist.net/images/anime/5/28371.jpg + .hack//Quantum + + + 4 + 10390 + 6161 + Bandai Visual, Kinema Citrus + http://cdn.myanimelist.net/images/anime/4/29050.jpg + .hack//Quantum Specials + + + 6 + 13311 + 6938 + + http://cdn.myanimelist.net/images/anime/4/37353.jpg + Henshin Gattai! 5 tsu no Atsuki Tamashii + + + 3 + 10013 + 5993 + Wao World + http://cdn.myanimelist.net/images/anime/8/41119.jpg + Shouwa Monogatari (Movie) + + + 6 + 10445 + 6184 + Madhouse Studios + http://cdn.myanimelist.net/images/anime/12/29356.jpg + Perfect Day + + + 5 + 9911 + 5937 + Gainax + http://cdn.myanimelist.net/images/anime/9/47741.jpg + Houkago no Pleiades + + + 4 + 6954 + 4804 + ufotable, Aniplex of America + http://cdn.myanimelist.net/images/anime/3/52557.jpg + Kara no Kyoukai: Epilogue + + + 2 + 10297 + 6117 + Dogakobo + http://cdn.myanimelist.net/images/anime/6/28330.jpg + Shin Koihime†Musou: Otome Tairan OVA + + + 4 + 15015 + 7197 + + http://cdn.myanimelist.net/images/anime/3/40773.jpg + Shin Koihime†Musou: Otome Tairan OVA Omake + + + 4 + 10302 + 6120 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/11/28340.jpg + Pokemon Diamond & Pearl Special + + + 2 + 10361 + 6149 + Satelight + http://cdn.myanimelist.net/images/anime/9/28487.jpg + Macross Frontier: Chou Jikuu Gekijou + + + 1 + 9893 + 5927 + Toei Animation + http://cdn.myanimelist.net/images/anime/4/28333.jpg + Suite Precure♪ + + + 1 + 10330 + 6131 + TMS Entertainment, Nelvana + http://cdn.myanimelist.net/images/anime/2/49725.jpg + Bakugan Battle Brawlers: Mechtanium Surge + + + 4 + 9940 + 5952 + P.A. Works + http://cdn.myanimelist.net/images/anime/11/27495.jpg + Mai no Mahou to Katei no Hi + + + 4 + 10020 + 5998 + Aniplex, AIC Build, Aniplex of America + http://cdn.myanimelist.net/images/anime/8/29734.jpg + Ore no Imouto ga Konnani Kawaii Wake ga Nai Specials + + + 2 + 8986 + 5562 + Madhouse Studios, Warner Bros. + http://cdn.myanimelist.net/images/anime/8/37135.jpg + Supernatural The Animation + + + 2 + 9347 + 5694 + Feel, Zexcs + http://cdn.myanimelist.net/images/anime/6/25892.jpg + Fortune Arterial: Akai Yakusoku - Tadoritsuita Basho + + + 2 + 9611 + 5808 + AIC + http://cdn.myanimelist.net/images/anime/5/28569.jpg + Aa! Megami-sama! (2011) + + + 2 + 9130 + 5612 + TMS Entertainment + http://cdn.myanimelist.net/images/anime/12/29597.jpg + Saint Seiya: The Lost Canvas - Meiou Shinwa 2 + + + 4 + 9396 + 5721 + Lantis, Bridge + http://cdn.myanimelist.net/images/anime/8/26045.jpg + Mitsudomoe Special + + + 4 + 10431 + 6178 + Silver Link + http://cdn.myanimelist.net/images/anime/12/29181.jpg + Baka to Test to Shoukanjuu: Matsuri Special + + + 2 + 9471 + 5745 + FUNimation Entertainment, Silver Link + http://cdn.myanimelist.net/images/anime/8/27463.jpg + Baka to Test to Shoukanjuu: Matsuri + + + 3 + 7222 + 4903 + Satelight, 8bit + http://cdn.myanimelist.net/images/anime/5/33137.jpg + Macross Frontier: Sayonara no Tsubasa + \ No newline at end of file diff --git a/data/db/season/2012_fall.xml b/data/db/season/2012_fall.xml index c3c5382db..373967416 100644 --- a/data/db/season/2012_fall.xml +++ b/data/db/season/2012_fall.xml @@ -1,868 +1,991 @@ - - - - Fall 2012 - 1380334748 - - - 2 - 15111 - WField, Next Media Animation - http://cdn.myanimelist.net/images/anime/7/40957.jpg - Spy Penguin - - - 6 - 14951 - Sunrise - http://cdn.myanimelist.net/images/anime/6/40809.jpg - Wonderful Rush - - - 2 - 12729 - Genco, Lantis, TNK, AT-X - http://cdn.myanimelist.net/images/anime/5/50817.jpg - High School DxD OVA - - - 1 - 14913 - Sunrise - http://cdn.myanimelist.net/images/anime/11/49195.jpg - Battle Spirits: Sword Eyes - - - 1 - 15865 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/13/42957.jpg - Tamagotchi! Yume Kira Dream - - - 2 - 13939 - Sunrise - http://cdn.myanimelist.net/images/anime/10/41815.jpg - Accel World EX - - - 5 - 10258 - Movic - http://cdn.myanimelist.net/images/anime/10/42417.jpg - Gokicha!! Cockroach Girl! - - - 5 - 15389 - DLE - http://cdn.myanimelist.net/images/anime/5/42271.jpg - Kremlin - - - 3 - 9544 - Sony Pictures Entertainment, Capcom - http://cdn.myanimelist.net/images/anime/8/42299.jpg - Biohazard: Damnation - - - 4 - 16574 - TV Tokyo, NAS - http://cdn.myanimelist.net/images/anime/3/44710.jpg - Yu-Gi-Oh! Zexal Special - - - 4 - 15617 - AIC A.S.T.A., Sentai Filmworks - http://cdn.myanimelist.net/images/anime/4/42473.jpg - Jinrui wa Suitai Shimashita Specials - - - 2 - 15127 - A-1 Pictures - http://cdn.myanimelist.net/images/anime/2/42397.jpg - Sakura Taisen: Kanadegumi - - - 2 - 14875 - Bones, Bandai Visual, FUNimation Entertainment, Project Eureka AO - http://cdn.myanimelist.net/images/anime/6/40311.jpg - Eureka Seven AO: Jungfrau no Hanabana-tachi - - - 6 - 17068 - - http://cdn.myanimelist.net/images/anime/8/45652.jpg - Transfer - - - 4 - 18639 - Production I.G, Xebec - http://cdn.myanimelist.net/images/anime/13/49767.jpg - Rinne no Lagrange Season 2 Picture Drama - - - 5 - 16608 - - http://cdn.myanimelist.net/images/anime/6/44758.jpg - Shitcom - - - 3 - 12015 - Sunrise, Viz Media - http://cdn.myanimelist.net/images/anime/2/40041.jpg - Tiger & Bunny Movie 1: The Beginning - - - 3 - 12053 - Starchild Records, GoHands, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/12/39309.jpg - Mardock Scramble: The Third Exhaust - - - 4 - 17419 - Seven Arcs - http://cdn.myanimelist.net/images/anime/5/46573.jpg - Dog Days' Specials - - - 4 - 13403 - Aniplex, David Production, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/10/41927.jpg - Inu x Boku SS Special - - - 4 - 15729 - Arms - http://cdn.myanimelist.net/images/anime/9/43079.jpg - Hagure Yuusha no Estetica: Hajirai Ippai - - - 4 - 18053 - - http://cdn.myanimelist.net/images/anime/5/47963.jpg - Koi to Senkyo to Chocolate: Ikenai Hazuki-sensei - - - 2 - 14753 - Hoods Entertainment - http://cdn.myanimelist.net/images/anime/2/40175.jpg - Hori-san to Miyamura-kun: Shingakki - - - 2 - 14027 - FUNimation Entertainment, AIC Build - http://cdn.myanimelist.net/images/anime/3/39215.jpg - Boku wa Tomodachi ga Sukunai: Add-on Disc - - - 5 - 15687 - Kyoto Animation - http://cdn.myanimelist.net/images/anime/9/42665.jpg - Chuunibyou demo Koi ga Shitai! Lite - - - 3 - 8475 - Toei Animation - http://cdn.myanimelist.net/images/anime/7/44289.jpg - Asura - - - 1 - 13125 - Aniplex, TV Asahi, A-1 Pictures, Pony Canyon, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/12/36775.jpg - Shinsekai yori - - - 4 - 15711 - J.C. Staff, NHK - http://cdn.myanimelist.net/images/anime/13/42673.jpg - Bakuman.: Deraman. - - - 1 - 15905 - Hang Zhou StarQ - http://cdn.myanimelist.net/images/anime/11/43831.jpg - Qin Shi Ming Yue 4: The Great Wall - - - 1 - 14645 - Studio Deen, Sentai Filmworks, Hiiro No Kakera Production Committee - http://cdn.myanimelist.net/images/anime/13/42709.jpg - Hiiro no Kakera Dai Ni Shou - - - 4 - 15719 - Madhouse Studios - http://cdn.myanimelist.net/images/anime/11/49101.jpg - Oda Nobuna no Yabou Soushuuhen - - - 1 - 15749 - Kachidoki Studio - http://cdn.myanimelist.net/images/anime/13/42733.jpg - Chiisana Oji-san - - - 4 - 15717 - TV Tokyo - http://cdn.myanimelist.net/images/anime/6/43769.jpg - Nagareboshi Lens Specials - - - 1 - 14713 - TV Tokyo, Dentsu, TMS Entertainment, FUNimation Entertainment, Pony Canyon - http://cdn.myanimelist.net/images/anime/3/41929.jpg - Kamisama Hajimemashita - - - 1 - 14989 - A-1 Pictures - http://cdn.myanimelist.net/images/anime/9/41937.jpg - Chousoku Henkei Gyrozetter - - - 1 - 14227 - TV Tokyo, Aniplex, Brains Base, NAS, Kodansha - http://cdn.myanimelist.net/images/anime/4/39779.jpg - Tonari no Kaibutsu-kun - - - 1 - 15045 - Kachidoki Studio - http://cdn.myanimelist.net/images/anime/11/40867.jpg - Litchi DE Hikari Club - - - 4 - 15951 - AIC - http://cdn.myanimelist.net/images/anime/4/43081.jpg - Amagami SS+ Plus Picture Drama - - - 4 - 15735 - White Fox - http://cdn.myanimelist.net/images/anime/3/43073.jpg - Jormungand: Perfect Order - First Stage Soushuuhen - - - 1 - 13185 - TV Tokyo, Dentsu, Dentsu Entertainment USA, Larx Entertainment - http://cdn.myanimelist.net/images/anime/11/40891.jpg - Juusen Battle Monsuno - - - 1 - 15313 - TV Tokyo, SANZIGEN - http://cdn.myanimelist.net/images/anime/2/41317.jpg - Wooser no Sono Higurashi - - - 1 - 14345 - Madhouse Studios, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/4/40977.jpg - Btooom! - - - 1 - 15417 - Sunrise, TV Tokyo, Aniplex - http://cdn.myanimelist.net/images/anime/4/43157.jpg - Gintama': Enchousen - - - 1 - 14653 - TV Tokyo, Geneon Universal Entertainment, Manglobe, Shogakukan Productions - http://cdn.myanimelist.net/images/anime/11/40711.jpg - Hayate no Gotoku! Can't Take My Eyes Off You - - - 1 - 14741 - Kyoto Animation, Lantis, Pony Canyon, TBS, Rakuonsha, Sentai Filmworks, Animation Do - http://cdn.myanimelist.net/images/anime/12/46931.jpg - Chuunibyou demo Koi ga Shitai! - - - 1 - 15897 - - http://cdn.myanimelist.net/images/anime/10/47085.jpg - Picchipichi Shizuku-chan - - - 5 - 15979 - Bones, Bandai Visual, Project Eureka AO - http://cdn.myanimelist.net/images/anime/7/43495.jpg - Eureka Seven AO: Aratanari Fukaki Ao - - - 1 - 14199 - FUNimation Entertainment, Lantis, Media Factory, AT-X, Silver Link, DAX Production, Cospa, Sony Music Communications, Nexus - http://cdn.myanimelist.net/images/anime/6/42111.jpg - Onii-chan Dakedo Ai Sae Areba Kankeinai yo ne! - - - 1 - 14467 - Starchild Records, Viz Media, Mainichi Broadcasting, GoHands, The Klock Worx - http://cdn.myanimelist.net/images/anime/3/47607.jpg - K - - - 1 - 14237 - Sentai Filmworks, 8bit - http://cdn.myanimelist.net/images/anime/10/43155.jpg - Busou Shinki - - - 1 - 11239 - Aniplex, Shaft, TBS, Movic, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/2/50501.jpg - Hidamari Sketch x Honeycomb - - - 2 - 14575 - Feel, Starchild Records - http://cdn.myanimelist.net/images/anime/8/53131.jpg - Minami-ke Omatase - - - 1 - 14075 - Bones, Aniplex, Dentsu, Square Enix, Mainichi Broadcasting, Aniplex of America, Yahoo! Japan - http://cdn.myanimelist.net/images/anime/7/42453.jpg - Zetsuen no Tempest - - - 1 - 12365 - J.C. Staff - http://cdn.myanimelist.net/images/anime/6/41845.jpg - Bakuman. 3 - - - 1 - 13655 - J.C. Staff, Key, Sentai Filmworks, Warner Bros. - http://cdn.myanimelist.net/images/anime/6/43757.jpg - Little Busters! - - - 1 - 13663 - Xebec, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/8/42217.jpg - To LOVE-Ru Darkness - - - 3 - 13439 - Nikkatsu Video - http://cdn.myanimelist.net/images/anime/8/42301.jpg - Shinpi no Hou - - - 1 - 14719 - David Production, Warner Bros. - http://cdn.myanimelist.net/images/anime/3/40409.jpg - JoJo's Bizarre Adventure (2012) - - - 1 - 15043 - Passione, Ryukyu Asahi Broadcasting - http://cdn.myanimelist.net/images/anime/12/45991.jpg - Haitai Nanafa - - - 3 - 11977 - Aniplex, Shaft, Mainichi Broadcasting, Movic, Nitroplus, Aniplex of America, Madoka Partners, Hobunsha - http://cdn.myanimelist.net/images/anime/6/42331.jpg - Mahou Shoujo Madoka★Magica Movie 1: Hajimari no Monogatari - - - 1 - 15125 - MAPPA, Earth Star Entertainment - http://cdn.myanimelist.net/images/anime/9/42163.jpg - Teekyuu - - - 1 - 15489 - TV Tokyo, NAS - http://cdn.myanimelist.net/images/anime/5/43491.jpg - Yu-Gi-Oh! Zexal Second - - - 1 - 14513 - Aniplex, Dentsu, A-1 Pictures, Shogakukan Productions, Mainichi Broadcasting, Aniplex of America - http://cdn.myanimelist.net/images/anime/11/42773.jpg - Magi: The Labyrinth of Magic - - - 1 - 11703 - Bandai Visual, FUNimation Entertainment, Lantis, Kodansha, Kinema Citrus, Memory-Tech, Bushiroad Inc. - http://cdn.myanimelist.net/images/anime/12/39629.jpg - Code:Breaker - - - 1 - 15545 - Kinema Citrus - http://cdn.myanimelist.net/images/anime/7/43197.jpg - Oshiri Kajiri Mushi (TV) - - - 1 - 15547 - TV Tokyo, d-rights - http://cdn.myanimelist.net/images/anime/2/42247.jpg - Cross Fight B-Daman eS - - - 1 - 14289 - Starchild Records, Kodansha, Zexcs, Magic Capsule, Sentai Filmworks, Yomiuri Advertising, GANSIS - http://cdn.myanimelist.net/images/anime/8/42509.jpg - Sukitte Ii na yo. - - - 1 - 14765 - Brains Base - http://cdn.myanimelist.net/images/anime/8/42863.jpg - Ixion Saga DT - - - 1 - 15061 - Sunrise, TV Tokyo, Bandai Visual, Dentsu - http://cdn.myanimelist.net/images/anime/3/40855.jpg - Aikatsu! - - - 1 - 14131 - Actas, Lantis, Movic, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/9/40969.jpg - Girls und Panzer - - - 1 - 13759 - J.C. Staff, Genco, Media Factory, Sentai Filmworks, ASCII Media Works - http://cdn.myanimelist.net/images/anime/4/43643.jpg - Sakurasou no Pet na Kanojo - - - 1 - 13331 - FUNimation Entertainment, White Fox - http://cdn.myanimelist.net/images/anime/9/42351.jpg - Jormungand: Perfect Order - - - 1 - 15787 - - http://cdn.myanimelist.net/images/anime/9/42817.jpg - Backstage Idol Story - - - 1 - 14527 - Gainax, TV Tokyo, Lantis, Sentai Filmworks, Asahi Production, Hakoniwa Academy Student Council - http://cdn.myanimelist.net/images/anime/13/41839.jpg - Medaka Box Abnormal - - - 1 - 13599 - Production I.G, Aniplex, FUNimation Entertainment, Fuji TV - http://cdn.myanimelist.net/images/anime/10/42013.jpg - Robotics;Notes - - - 1 - 13601 - Production I.G, FUNimation Entertainment, Fuji TV, Nitroplus, Sony Music Entertainment - http://cdn.myanimelist.net/images/anime/2/41995.jpg - Psycho-Pass - - - 2 - 14173 - - http://cdn.myanimelist.net/images/anime/7/39143.jpg - Upotte!! OVA - - - 3 - 11979 - Aniplex, Shaft, Mainichi Broadcasting, Movic, Nitroplus, Aniplex of America, Madoka Partners, Hobunsha - http://cdn.myanimelist.net/images/anime/6/42265.jpg - Mahou Shoujo Madoka★Magica Movie 2: Eien no Monogatari - - - 3 - 12509 - Xebec, AIC - http://cdn.myanimelist.net/images/anime/11/45925.jpg - Uchuu Senkan Yamato 2199 Movie 3: Hateshinaki Koukai - - - 5 - 10464 - AIC - http://cdn.myanimelist.net/images/anime/4/40621.jpg - Seitokai no Ichizon Lv.2 - - - 6 - 18831 - - http://cdn.myanimelist.net/images/anime/12/50283.jpg - Rinkaku - - - 2 - 15117 - Geneon Universal Entertainment, Manglobe - http://cdn.myanimelist.net/images/anime/11/43359.jpg - Kami nomi zo Shiru Sekai: Tenri-hen - - - 2 - 17359 - CoMix Wave - http://cdn.myanimelist.net/images/anime/3/46383.jpg - Peeping Life: The Perfect Explosion - - - 2 - 13693 - Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/12/38007.jpg - Ippatsu Hicchuu!! Devander - - - 3 - 14293 - Sunrise - http://cdn.myanimelist.net/images/anime/2/43807.jpg - Nerawareta Gakuen - - - 2 - 13303 - Soft Garage - http://cdn.myanimelist.net/images/anime/9/39507.jpg - Usogui - - - 3 - 13335 - TMS Entertainment, Asmik Ace Entertainment - http://cdn.myanimelist.net/images/anime/3/37313.jpg - Fuse Teppou Musume no Torimonochou - - - 4 - 15133 - DAX Production, Fifth Avenue - http://cdn.myanimelist.net/images/anime/4/43233.jpg - Aoi Sekai no Chuushin de - - - 2 - 14373 - Madhouse Studios, Aniplex, Kadokawa Shoten, ASCII Media Works - http://cdn.myanimelist.net/images/anime/6/42075.jpg - Arata naru Sekai: Mirai-hen - - - 3 - 15177 - Satelight, Big West, flying DOG - http://cdn.myanimelist.net/images/anime/11/41111.jpg - Macross FB7: Ginga Rukon - Ore no Uta wo Kike! - - - 2 - 15437 - Studio Deen, FUNimation Entertainment, Kadokawa Shoten, AT-X, Kadokawa Pictures Japan, The Klock Worx, flying DOG - http://cdn.myanimelist.net/images/anime/4/43515.jpg - Kore wa Zombie Desu ka? of the Dead OVA - - - 1 - 18601 - J.C. Staff - http://cdn.myanimelist.net/images/anime/7/49857.jpg - Dangerous Jiisan Ja (TV) - - - 4 - 15891 - TV Tokyo - http://cdn.myanimelist.net/images/anime/4/43767.jpg - Zekkyou Gakkyuu - - - 4 - 16099 - A-1 Pictures, Aniplex of America - http://cdn.myanimelist.net/images/anime/8/43461.jpg - Sword Art Online: Sword Art Offline - - - 2 - 16033 - Manglobe - http://cdn.myanimelist.net/images/anime/4/43467.jpg - Karneval - - - 2 - 14835 - A-1 Pictures - http://cdn.myanimelist.net/images/anime/12/43475.jpg - The iDOLM@STER: Shiny Festa - - - 5 - 16610 - - http://cdn.myanimelist.net/images/anime/6/44760.jpg - No Littering - - - 2 - 15781 - Gathering - http://cdn.myanimelist.net/images/anime/6/44678.jpg - Puchimas!: Petit iDOLM@STER - Dai 0 Wa - - - 3 - 15307 - Toei Animation - http://cdn.myanimelist.net/images/anime/6/42291.jpg - Smile Precure! Movie: Ehon no Naka wa Minna Chiguhagu! - - - 3 - 11755 - Production I.G, FUNimation Entertainment, SANZIGEN - http://cdn.myanimelist.net/images/anime/9/40189.jpg - 009 Re:Cyborg - - - 5 - 16101 - AIC A.S.T.A. - http://cdn.myanimelist.net/images/anime/3/45706.jpg - Jinrui wa Suitai Shimashita: Ningen-san no, Yousei-san Memo - - - 4 - 16233 - - http://cdn.myanimelist.net/images/anime/8/43711.jpg - Yowai Mushi - - - 3 - 7781 - Automatic Flowers Studio - http://cdn.myanimelist.net/images/anime/4/33985.jpg - Gothicmade: Hana no Utame - - - 4 - 15783 - TMS Entertainment - http://cdn.myanimelist.net/images/anime/2/45905.jpg - Lupin III: Touhou Kenbunroku - Another Page - - - 1 - 15059 - Avex Entertainment, SynergySP, Animax - http://cdn.myanimelist.net/images/anime/3/43573.jpg - Initial D Fifth Stage - - - 4 - 16273 - AIC - http://cdn.myanimelist.net/images/anime/12/43823.jpg - Acchi Kocchi: Place=Princess - - - 2 - 15439 - CoMix Wave, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/7/43465.jpg - Kono Danshi, Ningyo Hiroimashita. - - - 5 - 16690 - - http://cdn.myanimelist.net/images/anime/12/45008.jpg - My Life - - - 5 - 16389 - MAPPA, Solid Vox - http://cdn.myanimelist.net/images/anime/2/44219.jpg - Komachi to Dangorou: Lagoon Stone wo Sagase! - - - 4 - 15893 - TV Tokyo - http://cdn.myanimelist.net/images/anime/8/44498.jpg - Crash! - - - 4 - 16199 - Actas, Lantis, Movic, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/7/43631.jpg - Girls und Panzer: Shoukai Shimasu! - - - 3 - 3785 - Studio Khara, FUNimation Entertainment, The Klock Worx - http://cdn.myanimelist.net/images/anime/9/43201.jpg - Evangelion: 3.0 You Can (Not) Redo - - - 5 - 16331 - Production I.G - http://cdn.myanimelist.net/images/anime/4/44099.jpg - Next A-Class - - - 4 - 16001 - Starchild Records, Silver Link, Sentai Filmworks, Enterbrain - http://cdn.myanimelist.net/images/anime/10/45526.jpg - Kokoro Connect: Michi Random - - - 4 - 16335 - TV Tokyo, Satelight, Ixtl - http://cdn.myanimelist.net/images/anime/9/44133.jpg - Muv-Luv Alternative: Total Eclipse Recap - Climax Chokuzen Special - - - 4 - 16964 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/6/45434.jpg - Inazuma Eleven Go: TCG CM NG-shuu - - - 2 - 18713 - Xebec - http://cdn.myanimelist.net/images/anime/10/50123.jpg - Haiyore! Nyaruko-san: Yasashii Teki no Shitome-kata - - - 4 - 14189 - Silver Link, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/3/39145.jpg - Tasogare Otome x Amnesia: Taima Otome - - - 2 - 15535 - Diomedea - http://cdn.myanimelist.net/images/anime/6/42753.jpg - Yumekuri - - - 1 - 18133 - - http://cdn.myanimelist.net/images/anime/7/48719.jpg - Ikeike! Momon-chan - - - 3 - 13239 - Production I.G, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/13/40205.jpg - Mass Effect: Paragon Lost - - - 4 - 16694 - Studio Deen, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/12/44918.jpg - Sankarea: Wagahai mo... Zombie de Aru... - - - 4 - 16508 - - http://cdn.myanimelist.net/images/anime/6/44588.jpg - Ebiten: Kouritsu Ebisugawa Koukou Tenmonbu Specials - + + + + Fall 2012 + 1380334748 + + + 2 + 15111 + 7214 + WField, Next Media Animation + http://cdn.myanimelist.net/images/anime/7/40957.jpg + Spy Penguin + + + 6 + 14951 + 7188 + Sunrise + http://cdn.myanimelist.net/images/anime/6/40809.jpg + Wonderful Rush + + + 2 + 12729 + 6801 + Genco, Lantis, TNK, AT-X + http://cdn.myanimelist.net/images/anime/5/50817.jpg + High School DxD OVA + + + 1 + 14913 + 7181 + Sunrise + http://cdn.myanimelist.net/images/anime/11/49195.jpg + Battle Spirits: Sword Eyes + + + 1 + 15865 + 7326 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/13/42957.jpg + Tamagotchi! Yume Kira Dream + + + 2 + 13939 + 7057 + Sunrise + http://cdn.myanimelist.net/images/anime/10/41815.jpg + Accel World EX + + + 5 + 10258 + 6102 + Movic + http://cdn.myanimelist.net/images/anime/10/42417.jpg + Gokicha!! Cockroach Girl! + + + 5 + 15389 + 7246 + DLE + http://cdn.myanimelist.net/images/anime/5/42271.jpg + Kremlin + + + 3 + 9544 + 5784 + Sony Pictures Entertainment, Capcom + http://cdn.myanimelist.net/images/anime/8/42299.jpg + Biohazard: Damnation + + + 4 + 16574 + 7465 + TV Tokyo, NAS + http://cdn.myanimelist.net/images/anime/3/44710.jpg + Yu-Gi-Oh! Zexal Special + + + 4 + 15617 + 7285 + AIC A.S.T.A., Sentai Filmworks + http://cdn.myanimelist.net/images/anime/4/42473.jpg + Jinrui wa Suitai Shimashita Specials + + + 2 + 15127 + 7218 + A-1 Pictures + http://cdn.myanimelist.net/images/anime/2/42397.jpg + Sakura Taisen: Kanadegumi + + + 2 + 14875 + 7178 + Bones, Bandai Visual, FUNimation Entertainment, Project Eureka AO + http://cdn.myanimelist.net/images/anime/6/40311.jpg + Eureka Seven AO: Jungfrau no Hanabana-tachi + + + 6 + 17068 + + + http://cdn.myanimelist.net/images/anime/8/45652.jpg + Transfer + + + 4 + 18639 + 7919 + Production I.G, Xebec + http://cdn.myanimelist.net/images/anime/13/49767.jpg + Rinne no Lagrange Season 2 Picture Drama + + + 5 + 16608 + 7473 + + http://cdn.myanimelist.net/images/anime/6/44758.jpg + Shitcom + + + 3 + 12015 + 6643 + Sunrise, Viz Media + http://cdn.myanimelist.net/images/anime/2/40041.jpg + Tiger & Bunny Movie 1: The Beginning + + + 3 + 12053 + 6653 + Starchild Records, GoHands, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/12/39309.jpg + Mardock Scramble: The Third Exhaust + + + 4 + 17419 + + Seven Arcs + http://cdn.myanimelist.net/images/anime/5/46573.jpg + Dog Days' Specials + + + 4 + 13403 + 6954 + Aniplex, David Production, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/10/41927.jpg + Inu x Boku SS Special + + + 4 + 15729 + 7298 + Arms + http://cdn.myanimelist.net/images/anime/9/43079.jpg + Hagure Yuusha no Estetica: Hajirai Ippai + + + 4 + 18053 + + + http://cdn.myanimelist.net/images/anime/5/47963.jpg + Koi to Senkyo to Chocolate: Ikenai Hazuki-sensei + + + 2 + 14753 + 7164 + Hoods Entertainment + http://cdn.myanimelist.net/images/anime/2/40175.jpg + Hori-san to Miyamura-kun: Shingakki + + + 2 + 14027 + 7069 + FUNimation Entertainment, AIC Build + http://cdn.myanimelist.net/images/anime/3/39215.jpg + Boku wa Tomodachi ga Sukunai: Add-on Disc + + + 5 + 15687 + 7293 + Kyoto Animation + http://cdn.myanimelist.net/images/anime/9/42665.jpg + Chuunibyou demo Koi ga Shitai! Lite + + + 3 + 8475 + 5348 + Toei Animation + http://cdn.myanimelist.net/images/anime/7/44289.jpg + Asura + + + 1 + 13125 + 6887 + Aniplex, TV Asahi, A-1 Pictures, Pony Canyon, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/12/36775.jpg + Shinsekai yori + + + 4 + 15711 + 7296 + J.C. Staff, NHK + http://cdn.myanimelist.net/images/anime/13/42673.jpg + Bakuman.: Deraman. + + + 1 + 15905 + 7337 + Hang Zhou StarQ + http://cdn.myanimelist.net/images/anime/11/43831.jpg + Qin Shi Ming Yue 4: The Great Wall + + + 1 + 14645 + 7146 + Studio Deen, Sentai Filmworks, Hiiro No Kakera Production Committee + http://cdn.myanimelist.net/images/anime/13/42709.jpg + Hiiro no Kakera Dai Ni Shou + + + 4 + 15719 + 6633 + Madhouse Studios + http://cdn.myanimelist.net/images/anime/11/49101.jpg + Oda Nobuna no Yabou Soushuuhen + + + 1 + 15749 + 7302 + Kachidoki Studio + http://cdn.myanimelist.net/images/anime/13/42733.jpg + Chiisana Oji-san + + + 4 + 15717 + 7297 + TV Tokyo + http://cdn.myanimelist.net/images/anime/6/43769.jpg + Nagareboshi Lens Specials + + + 1 + 14713 + 7157 + TV Tokyo, Dentsu, TMS Entertainment, FUNimation Entertainment, Pony Canyon + http://cdn.myanimelist.net/images/anime/3/41929.jpg + Kamisama Hajimemashita + + + 1 + 14989 + 7191 + A-1 Pictures + http://cdn.myanimelist.net/images/anime/9/41937.jpg + Chousoku Henkei Gyrozetter + + + 1 + 14227 + 7099 + TV Tokyo, Aniplex, Brains Base, NAS, Kodansha + http://cdn.myanimelist.net/images/anime/4/39779.jpg + Tonari no Kaibutsu-kun + + + 1 + 15045 + 7202 + Kachidoki Studio + http://cdn.myanimelist.net/images/anime/11/40867.jpg + Litchi DE Hikari Club + + + 4 + 15951 + 7345 + AIC + http://cdn.myanimelist.net/images/anime/4/43081.jpg + Amagami SS+ Plus Picture Drama + + + 4 + 15735 + 7300 + White Fox + http://cdn.myanimelist.net/images/anime/3/43073.jpg + Jormungand: Perfect Order - First Stage Soushuuhen + + + 1 + 13185 + 6907 + TV Tokyo, Dentsu, Dentsu Entertainment USA, Larx Entertainment + http://cdn.myanimelist.net/images/anime/11/40891.jpg + Juusen Battle Monsuno + + + 1 + 15313 + 7238 + TV Tokyo, SANZIGEN + http://cdn.myanimelist.net/images/anime/2/41317.jpg + Wooser no Sono Higurashi + + + 1 + 14345 + 7113 + Madhouse Studios, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/4/40977.jpg + Btooom! + + + 1 + 15417 + 7253 + Sunrise, TV Tokyo, Aniplex + http://cdn.myanimelist.net/images/anime/4/43157.jpg + Gintama': Enchousen + + + 1 + 14653 + 7148 + TV Tokyo, Geneon Universal Entertainment, Manglobe, Shogakukan Productions + http://cdn.myanimelist.net/images/anime/11/40711.jpg + Hayate no Gotoku! Can't Take My Eyes Off You + + + 1 + 14741 + 7160 + Kyoto Animation, Lantis, Pony Canyon, TBS, Rakuonsha, Sentai Filmworks, Animation Do + http://cdn.myanimelist.net/images/anime/12/46931.jpg + Chuunibyou demo Koi ga Shitai! + + + 1 + 15897 + 7336 + + http://cdn.myanimelist.net/images/anime/10/47085.jpg + Picchipichi Shizuku-chan + + + 5 + 15979 + 7352 + Bones, Bandai Visual, Project Eureka AO + http://cdn.myanimelist.net/images/anime/7/43495.jpg + Eureka Seven AO: Aratanari Fukaki Ao + + + 1 + 14199 + 7094 + FUNimation Entertainment, Lantis, Media Factory, AT-X, Silver Link, DAX Production, Cospa, Sony Music Communications, Nexus + http://cdn.myanimelist.net/images/anime/6/42111.jpg + Onii-chan Dakedo Ai Sae Areba Kankeinai yo ne! + + + 1 + 14467 + 7125 + Starchild Records, Viz Media, Mainichi Broadcasting, GoHands, The Klock Worx + http://cdn.myanimelist.net/images/anime/3/47607.jpg + K + + + 1 + 14237 + 7102 + Sentai Filmworks, 8bit + http://cdn.myanimelist.net/images/anime/10/43155.jpg + Busou Shinki + + + 1 + 11239 + 6480 + Aniplex, Shaft, TBS, Movic, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/2/50501.jpg + Hidamari Sketch x Honeycomb + + + 2 + 14575 + 7138 + Feel, Starchild Records + http://cdn.myanimelist.net/images/anime/8/53131.jpg + Minami-ke Omatase + + + 1 + 14075 + 7078 + Bones, Aniplex, Dentsu, Square Enix, Mainichi Broadcasting, Aniplex of America, Yahoo! Japan + http://cdn.myanimelist.net/images/anime/7/42453.jpg + Zetsuen no Tempest + + + 1 + 12365 + 6714 + J.C. Staff + http://cdn.myanimelist.net/images/anime/6/41845.jpg + Bakuman. 3 + + + 1 + 13655 + 7005 + J.C. Staff, Key, Sentai Filmworks, Warner Bros. + http://cdn.myanimelist.net/images/anime/6/43757.jpg + Little Busters! + + + 1 + 13663 + 7007 + Xebec, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/8/42217.jpg + To LOVE-Ru Darkness + + + 3 + 13439 + 6966 + Nikkatsu Video + http://cdn.myanimelist.net/images/anime/7/56551.jpg + Shinpi no Hou + + + 1 + 14719 + 7158 + David Production, Warner Bros. + http://cdn.myanimelist.net/images/anime/3/40409.jpg + JoJo's Bizarre Adventure (2012) + + + 1 + 15043 + 7201 + Passione, Ryukyu Asahi Broadcasting + http://cdn.myanimelist.net/images/anime/12/45991.jpg + Haitai Nanafa + + + 3 + 11977 + 6636 + Aniplex, Shaft, Mainichi Broadcasting, Movic, Nitroplus, Aniplex of America, Madoka Partners, Hobunsha + http://cdn.myanimelist.net/images/anime/6/42331.jpg + Mahou Shoujo Madoka★Magica Movie 1: Hajimari no Monogatari + + + 1 + 15125 + 7217 + MAPPA, Earth Star Entertainment + http://cdn.myanimelist.net/images/anime/9/42163.jpg + Teekyuu + + + 1 + 15489 + 7263 + TV Tokyo, NAS + http://cdn.myanimelist.net/images/anime/5/43491.jpg + Yu-Gi-Oh! Zexal Second + + + 1 + 14513 + 7129 + Aniplex, Dentsu, A-1 Pictures, Shogakukan Productions, Mainichi Broadcasting, Aniplex of America + http://cdn.myanimelist.net/images/anime/11/42773.jpg + Magi: The Labyrinth of Magic + + + 1 + 11703 + 6570 + Bandai Visual, FUNimation Entertainment, Lantis, Kodansha, Kinema Citrus, Memory-Tech, Bushiroad Inc. + http://cdn.myanimelist.net/images/anime/12/39629.jpg + Code:Breaker + + + 1 + 15545 + 7273 + Kinema Citrus + http://cdn.myanimelist.net/images/anime/7/43197.jpg + Oshiri Kajiri Mushi (TV) + + + 1 + 15547 + 7274 + TV Tokyo, d-rights + http://cdn.myanimelist.net/images/anime/2/42247.jpg + Cross Fight B-Daman eS + + + 1 + 14289 + 7109 + Starchild Records, Kodansha, Zexcs, Magic Capsule, Sentai Filmworks, Yomiuri Advertising, GANSIS + http://cdn.myanimelist.net/images/anime/8/42509.jpg + Sukitte Ii na yo. + + + 1 + 14765 + 7165 + Brains Base + http://cdn.myanimelist.net/images/anime/8/42863.jpg + Ixion Saga DT + + + 1 + 15061 + 7205 + Sunrise, TV Tokyo, Bandai Visual, Dentsu + http://cdn.myanimelist.net/images/anime/3/40855.jpg + Aikatsu! + + + 1 + 14131 + 7087 + Actas, Lantis, Movic, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/9/40969.jpg + Girls und Panzer + + + 1 + 13759 + 7023 + J.C. Staff, Genco, Media Factory, Sentai Filmworks, ASCII Media Works + http://cdn.myanimelist.net/images/anime/4/43643.jpg + Sakurasou no Pet na Kanojo + + + 1 + 13331 + 6940 + FUNimation Entertainment, White Fox + http://cdn.myanimelist.net/images/anime/9/42351.jpg + Jormungand: Perfect Order + + + 1 + 15787 + 7310 + + http://cdn.myanimelist.net/images/anime/9/42817.jpg + Backstage Idol Story + + + 1 + 14527 + 7132 + Gainax, TV Tokyo, Lantis, Sentai Filmworks, Asahi Production, Hakoniwa Academy Student Council + http://cdn.myanimelist.net/images/anime/13/41839.jpg + Medaka Box Abnormal + + + 1 + 13599 + 6999 + Production I.G, Aniplex, FUNimation Entertainment, Fuji TV + http://cdn.myanimelist.net/images/anime/10/42013.jpg + Robotics;Notes + + + 1 + 13601 + 7000 + Production I.G, FUNimation Entertainment, Fuji TV, Nitroplus, Sony Music Entertainment + http://cdn.myanimelist.net/images/anime/2/41995.jpg + Psycho-Pass + + + 2 + 14173 + 7089 + Xebec + http://cdn.myanimelist.net/images/anime/7/39143.jpg + Upotte!! OVA + + + 3 + 11979 + 6637 + Aniplex, Shaft, Mainichi Broadcasting, Movic, Nitroplus, Aniplex of America, Madoka Partners, Hobunsha + http://cdn.myanimelist.net/images/anime/6/42265.jpg + Mahou Shoujo Madoka★Magica Movie 2: Eien no Monogatari + + + 3 + 12509 + 6753 + Xebec, AIC + http://cdn.myanimelist.net/images/anime/11/45925.jpg + Uchuu Senkan Yamato 2199 Movie 3: Hateshinaki Koukai + + + 5 + 10464 + 6193 + AIC + http://cdn.myanimelist.net/images/anime/4/40621.jpg + Seitokai no Ichizon Lv.2 + + + 6 + 18831 + + + http://cdn.myanimelist.net/images/anime/12/50283.jpg + Rinkaku + + + 2 + 15117 + 7215 + Geneon Universal Entertainment, Manglobe + http://cdn.myanimelist.net/images/anime/11/43359.jpg + Kami nomi zo Shiru Sekai: Tenri-hen + + + 2 + 17359 + 7764 + CoMix Wave + http://cdn.myanimelist.net/images/anime/3/46383.jpg + Peeping Life: The Perfect Explosion + + + 2 + 13693 + 7016 + Tatsunoko Productions + http://cdn.myanimelist.net/images/anime/12/38007.jpg + Ippatsu Hicchuu!! Devander + + + 3 + 14293 + 7110 + Sunrise + http://cdn.myanimelist.net/images/anime/2/43807.jpg + Nerawareta Gakuen + + + 2 + 13303 + 6935 + Soft Garage + http://cdn.myanimelist.net/images/anime/9/39507.jpg + Usogui + + + 3 + 13335 + 6942 + TMS Entertainment, Asmik Ace Entertainment + http://cdn.myanimelist.net/images/anime/3/37313.jpg + Fuse Teppou Musume no Torimonochou + + + 4 + 15133 + 7220 + DAX Production, Fifth Avenue + http://cdn.myanimelist.net/images/anime/4/43233.jpg + Aoi Sekai no Chuushin de + + + 2 + 14373 + 7120 + Madhouse Studios, Aniplex, Kadokawa Shoten, ASCII Media Works + http://cdn.myanimelist.net/images/anime/6/42075.jpg + Arata naru Sekai: Mirai-hen + + + 3 + 15177 + 7225 + Satelight, Big West, flying DOG + http://cdn.myanimelist.net/images/anime/11/41111.jpg + Macross FB7: Ginga Rukon - Ore no Uta wo Kike! + + + 2 + 15437 + 7256 + Studio Deen, FUNimation Entertainment, Kadokawa Shoten, AT-X, Kadokawa Pictures Japan, The Klock Worx, flying DOG + http://cdn.myanimelist.net/images/anime/4/43515.jpg + Kore wa Zombie Desu ka? of the Dead OVA + + + 1 + 18601 + + J.C. Staff + http://cdn.myanimelist.net/images/anime/7/49857.jpg + Dangerous Jiisan Ja (TV) + + + 4 + 15891 + 7333 + TV Tokyo + http://cdn.myanimelist.net/images/anime/4/43767.jpg + Zekkyou Gakkyuu + + + 4 + 16099 + 7372 + A-1 Pictures, Aniplex of America + http://cdn.myanimelist.net/images/anime/8/43461.jpg + Sword Art Online: Sword Art Offline + + + 2 + 16033 + 7363 + Manglobe + http://cdn.myanimelist.net/images/anime/4/43467.jpg + Karneval + + + 2 + 14835 + 7175 + A-1 Pictures + http://cdn.myanimelist.net/images/anime/12/43475.jpg + The iDOLM@STER: Shiny Festa + + + 5 + 16610 + 7474 + + http://cdn.myanimelist.net/images/anime/6/44760.jpg + No Littering + + + 2 + 15781 + 7307 + Gathering + http://cdn.myanimelist.net/images/anime/6/44678.jpg + Puchimas!: Petit iDOLM@STER - Dai 0 Wa + + + 3 + 15307 + 7237 + Toei Animation + http://cdn.myanimelist.net/images/anime/6/42291.jpg + Smile Precure! Movie: Ehon no Naka wa Minna Chiguhagu! + + + 3 + 11755 + 6588 + Production I.G, FUNimation Entertainment, SANZIGEN + http://cdn.myanimelist.net/images/anime/9/40189.jpg + 009 Re:Cyborg + + + 5 + 16101 + 7373 + AIC A.S.T.A. + http://cdn.myanimelist.net/images/anime/3/45706.jpg + Jinrui wa Suitai Shimashita: Ningen-san no, Yousei-san Memo + + + 4 + 16233 + 7389 + + http://cdn.myanimelist.net/images/anime/8/43711.jpg + Yowai Mushi + + + 3 + 7781 + 5120 + Automatic Flowers Studio + http://cdn.myanimelist.net/images/anime/4/33985.jpg + Gothicmade: Hana no Utame + + + 4 + 15783 + 7308 + TMS Entertainment + http://cdn.myanimelist.net/images/anime/2/45905.jpg + Lupin III: Touhou Kenbunroku - Another Page + + + 1 + 15059 + 7204 + Avex Entertainment, SynergySP, Animax + http://cdn.myanimelist.net/images/anime/3/43573.jpg + Initial D Fifth Stage + + + 4 + 16273 + 7401 + AIC + http://cdn.myanimelist.net/images/anime/12/43823.jpg + Acchi Kocchi: Place=Princess + + + 2 + 15439 + 7257 + CoMix Wave, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/7/43465.jpg + Kono Danshi, Ningyo Hiroimashita. + + + 5 + 16690 + 7490 + + http://cdn.myanimelist.net/images/anime/12/45008.jpg + My Life + + + 5 + 16389 + 7418 + MAPPA, Solid Vox + http://cdn.myanimelist.net/images/anime/2/44219.jpg + Komachi to Dangorou: Lagoon Stone wo Sagase! + + + 4 + 15893 + 7334 + TV Tokyo + http://cdn.myanimelist.net/images/anime/8/44498.jpg + Crash! + + + 4 + 16199 + 7386 + Actas, Lantis, Movic, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/7/43631.jpg + Girls und Panzer: Shoukai Shimasu! + + + 3 + 3785 + 3251 + Studio Khara, FUNimation Entertainment, The Klock Worx + http://cdn.myanimelist.net/images/anime/9/43201.jpg + Evangelion: 3.0 You Can (Not) Redo + + + 5 + 16331 + 7406 + Production I.G + http://cdn.myanimelist.net/images/anime/4/44099.jpg + Next A-Class + + + 4 + 16001 + 7355 + Starchild Records, Silver Link, Sentai Filmworks, Enterbrain + http://cdn.myanimelist.net/images/anime/10/45526.jpg + Kokoro Connect: Michi Random + + + 4 + 16335 + 7407 + TV Tokyo, Satelight, Ixtl + http://cdn.myanimelist.net/images/anime/9/44133.jpg + Muv-Luv Alternative: Total Eclipse Recap - Climax Chokuzen Special + + + 4 + 16964 + 7559 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/6/45434.jpg + Inazuma Eleven Go: TCG CM NG-shuu + + + 2 + 18713 + 7752 + Xebec + http://cdn.myanimelist.net/images/anime/10/50123.jpg + Haiyore! Nyaruko-san: Yasashii Teki no Shitome-kata + + + 4 + 14189 + 7093 + Silver Link, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/3/39145.jpg + Tasogare Otome x Amnesia: Taima Otome + + + 2 + 15535 + 7269 + Diomedea + http://cdn.myanimelist.net/images/anime/6/42753.jpg + Yumekuri + + + 1 + 18133 + + + http://cdn.myanimelist.net/images/anime/7/48719.jpg + Ikeike! Momon-chan + + + 3 + 13239 + 6919 + Production I.G, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/13/40205.jpg + Mass Effect: Paragon Lost + + + 4 + 16694 + 7492 + Studio Deen, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/12/44918.jpg + Sankarea: Wagahai mo... Zombie de Aru... + + + 4 + 16508 + 7444 + + http://cdn.myanimelist.net/images/anime/6/44588.jpg + Ebiten: Kouritsu Ebisugawa Koukou Tenmonbu Specials + \ No newline at end of file diff --git a/data/db/season/2012_spring.xml b/data/db/season/2012_spring.xml index 2e9161daf..c949e4a0d 100644 --- a/data/db/season/2012_spring.xml +++ b/data/db/season/2012_spring.xml @@ -1,910 +1,1039 @@ - - - - Spring 2012 - 1380310542 - - - 2 - 11209 - Xebec, AIC, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/7/36929.jpg - Maken-Ki! OVA - - - 5 - 12237 - Marvy Jack - http://cdn.myanimelist.net/images/anime/12/39121.jpg - Koi-ken!: Watashitachi Anime ni Nacchatta! - - - 3 - 11053 - Tokyo Movie Shinsha - http://cdn.myanimelist.net/images/anime/6/49775.jpg - Doraemon: Nobita and the Miracle Island - Animal Adventure - - - 3 - 13169 - Telecom Animation Film - http://cdn.myanimelist.net/images/anime/11/50405.jpg - Buta - - - 6 - 17949 - Sony Music Entertainment, Carp Studio - http://cdn.myanimelist.net/images/anime/12/47807.jpg - The Everlasting Guilty Crown - - - 4 - 13263 - Aniplex, ufotable, Aniplex of America - http://cdn.myanimelist.net/images/anime/6/54109.jpg - Fate/Zero: Onegai! Einzbern Soudanshitsu - - - 3 - 12223 - Sunrise - http://cdn.myanimelist.net/images/anime/11/36835.jpg - Scryed Alteration II: Quan - - - 6 - 15673 - - http://cdn.myanimelist.net/images/anime/2/42663.jpg - Tell Your World - - - 3 - 13171 - Production I.G - http://cdn.myanimelist.net/images/anime/11/38877.jpg - Wasurenagumo - - - 5 - 13251 - Production I.G, Studio 4°C, Shaft - http://cdn.myanimelist.net/images/anime/4/37343.jpg - Shin Hikari Shinwa: Palutena no Kagami - - - 4 - 13823 - - http://cdn.myanimelist.net/images/anime/12/38367.jpg - Kamiusagi Rope Movie Episode 0 - - - 4 - 13825 - - http://cdn.myanimelist.net/images/anime/5/38517.jpg - Kamiusagi Rope x AU Collaboration - - - 4 - 13435 - - http://cdn.myanimelist.net/images/anime/6/37617.jpg - Taka no Tsume NEO Announcement Movie - - - 2 - 11813 - Brains Base - http://cdn.myanimelist.net/images/anime/7/39893.jpg - Shijou Saikyou no Deshi Kenichi OVA - - - 2 - 11313 - Tatsunoko Productions, Kodansha - http://cdn.myanimelist.net/images/anime/10/35843.jpg - Kimi no Iru Machi: Tasogare Kousaten - - - 1 - 12677 - Gonzo, WOWOW, Pony Canyon, Planet, LandQ studios, Viki, Slowcurve - http://cdn.myanimelist.net/images/anime/4/35573.jpg - Ozma - - - 3 - 9751 - AIC, NTT Docomo, 501st JOINT FIGHTER WING - http://cdn.myanimelist.net/images/anime/11/34907.jpg - Strike Witches the Movie - - - 3 - 12221 - Toei Animation, Marvelous AQL - http://cdn.myanimelist.net/images/anime/8/39609.jpg - Precure All Stars New Stage: Mirai no Tomodachi - - - 2 - 13833 - Kinema Citrus, Advance Syakujii - http://cdn.myanimelist.net/images/anime/7/38377.jpg - Nagareboshi Lens - - - 2 - 13835 - Kinema Citrus - http://cdn.myanimelist.net/images/anime/12/38379.jpg - Marimo no Hana: Saikyou Butouha Shougakusei Densetsu - - - 4 - 13497 - Ashi Productions - http://cdn.myanimelist.net/images/anime/9/37685.jpg - Neko no Sumu Shima - - - 4 - 13827 - - http://cdn.myanimelist.net/images/anime/7/38519.jpg - Kamiusagi Rope x Panasonic Collaboration - - - 5 - 13359 - - http://cdn.myanimelist.net/images/anime/8/37547.jpg - PistStar - - - 4 - 13173 - The Answer Studio - http://cdn.myanimelist.net/images/anime/9/40249.jpg - Puka Puka Juju - - - 4 - 13357 - Genco, FUNimation Entertainment, Lantis, TNK - http://cdn.myanimelist.net/images/anime/10/38449.jpg - High School DxD Specials - - - 2 - 10936 - AIC Plus+, NIS America, Inc. - http://cdn.myanimelist.net/images/anime/4/30178.jpg - Nekogami Yaoyorozu: Ohanami Ghostbusters - - - 5 - 13563 - - http://cdn.myanimelist.net/images/anime/13/37765.jpg - The Four Seasons - - - 2 - 11005 - Cammot - http://cdn.myanimelist.net/images/anime/10/41841.jpg - Holy Knight - - - 2 - 13479 - Milky Cartoon - http://cdn.myanimelist.net/images/anime/4/37631.jpg - Uchuu Kyoudai: Apo's Dream - - - 4 - 13495 - Production I.G - http://cdn.myanimelist.net/images/anime/12/47557.jpg - Tokyo Disney Resort: Yume ga Kanau Basho - - - 5 - 16604 - - http://cdn.myanimelist.net/images/anime/11/44752.jpg - Pinky - - - 4 - 14583 - Production I.G, Bandai Visual, Xebec, flying DOG, Dwango - http://cdn.myanimelist.net/images/anime/13/39747.jpg - Rinne no Lagrange Specials - - - 3 - 13253 - Ordet - http://cdn.myanimelist.net/images/anime/11/37105.jpg - Blossom - - - 4 - 13183 - Aniplex, ufotable, Nitroplus, Aniplex of America - http://cdn.myanimelist.net/images/anime/6/37081.jpg - Fate/Zero Remix - - - 1 - 12875 - NHK, NAS, NHK Enterprises, TYO Animations - http://cdn.myanimelist.net/images/anime/8/36117.jpg - Ginga e Kickoff!! - - - 4 - 13175 - Shirogumi - http://cdn.myanimelist.net/images/anime/4/50407.jpg - Shiranpuri - - - 4 - 13247 - Tokyo Movie Shinsha - http://cdn.myanimelist.net/images/anime/2/46379.jpg - Lupin III: Lupin Ikka Seizoroi - - - 4 - 13249 - TMS Entertainment - http://cdn.myanimelist.net/images/anime/3/50251.jpg - Lupin VIII - - - 4 - 17811 - Kyoto Animation, Lantis, Rakuonsha - http://cdn.myanimelist.net/images/anime/10/47441.jpg - Kyoto Animation: Hassou-hen - - - 1 - 13141 - DAX Production, Dream Creation, Hotline - http://cdn.myanimelist.net/images/anime/7/38283.jpg - Shiba Inuko-san - - - 4 - 13669 - - http://cdn.myanimelist.net/images/anime/4/37931.jpg - Owari no Chronicle - - - 1 - 13163 - TV Tokyo, Tomason, Sony Music Entertainment, Peter Pan Creation - http://cdn.myanimelist.net/images/anime/4/36869.jpg - Furusato Saisei: Nihon no Mukashi Banashi - - - 4 - 13677 - - http://cdn.myanimelist.net/images/anime/6/37993.jpg - Ao no Exorcist (Movie) Special - - - 1 - 12929 - Toei Animation - http://cdn.myanimelist.net/images/anime/12/43597.jpg - Saint Seiya Omega - - - 1 - 12431 - Aniplex, Dentsu, A-1 Pictures, YTV, Sentai Filmworks, Japan Aerospace Exploration Agency, Trinity Sound - http://cdn.myanimelist.net/images/anime/7/37573.jpg - Uchuu Kyoudai - - - 1 - 12461 - Bandai Visual, Studio Deen, Lantis, YTV, DAX Production, Sentai Filmworks, Enterbrain, Hiiro No Kakera Production Committee - http://cdn.myanimelist.net/images/anime/3/36925.jpg - Hiiro no Kakera - - - 1 - 13165 - - http://cdn.myanimelist.net/images/anime/11/36873.jpg - Paboo & Mojies - - - 1 - 13463 - - http://cdn.myanimelist.net/images/anime/8/37609.jpg - Panda no Taputapu - - - 1 - 7867 - Kodansha, Daewon Media - http://cdn.myanimelist.net/images/anime/6/36931.jpg - Gon - - - 1 - 18941 - - http://cdn.myanimelist.net/images/anime/9/50737.jpg - Shimajirou no Wow! - - - 1 - 11837 - TV Asahi, TMS Entertainment, Viz Media, Asatsu DK, YTV, Toho Company, NYAV Post, Sakura Create - http://cdn.myanimelist.net/images/anime/7/35529.jpg - Zetman - - - 1 - 11859 - Arms, Genco, Media Factory, AT-X, Sentai Filmworks, Hobby Japan, NYAV Post - http://cdn.myanimelist.net/images/anime/3/35023.jpg - Queen's Blade: Rebellion - - - 1 - 13139 - NHK - http://cdn.myanimelist.net/images/anime/2/36807.jpg - Gakkatsu! - - - 1 - 12123 - DAX Production, Takeshobo, C2C, Right Gauge, Dwango Music Entertainment - http://cdn.myanimelist.net/images/anime/3/36705.jpg - Yurumates 3D - - - 4 - 12893 - Sunrise - http://cdn.myanimelist.net/images/anime/8/38527.jpg - Danshi Koukousei no Nichijou Specials - - - 1 - 13455 - Egg - http://cdn.myanimelist.net/images/anime/7/46983.jpg - Zumomo to Nupepe - - - 1 - 12979 - Studio Pierrot, TV Tokyo, Viz Media - http://cdn.myanimelist.net/images/anime/13/36475.jpg - Rock Lee no Seishun Full-Power Ninden - - - 1 - 11739 - J.C. Staff, TV Tokyo, Aniplex, Square Enix, NAS, Movic, Kimi To Boku Production Partners - http://cdn.myanimelist.net/images/anime/10/42107.jpg - Kimi to Boku. 2 - - - 1 - 13377 - Takeshobo, Seven - http://cdn.myanimelist.net/images/anime/11/38901.jpg - Recorder to Randoseru Re♪ - - - 1 - 13159 - NHK, Shin-Ei Animation - http://cdn.myanimelist.net/images/anime/6/36853.jpg - Kuromajo-san ga Tooru!! - - - 4 - 12669 - AIC - http://cdn.myanimelist.net/images/anime/9/35567.jpg - Amagami SS+ Plus Specials - - - 1 - 13459 - - http://cdn.myanimelist.net/images/anime/13/48753.jpg - Ribbon-chan - - - 1 - 13029 - Sparky Animation - http://cdn.myanimelist.net/images/anime/4/36603.jpg - Arashi no Yoru ni: Himitsu no Tomodachi - - - 1 - 12815 - Studio Pierrot, TV Tokyo, Avex Entertainment - http://cdn.myanimelist.net/images/anime/10/36803.jpg - Shirokuma Cafe - - - 1 - 10790 - Studio Deen, FUNimation Entertainment, Kadokawa Shoten, AT-X, Kadokawa Pictures Japan, The Klock Worx, flying DOG - http://cdn.myanimelist.net/images/anime/4/37451.jpg - Kore wa Zombie Desu ka? of the Dead - - - 1 - 13203 - TMS Entertainment, FUNimation Entertainment, Po10tial - http://cdn.myanimelist.net/images/anime/3/41291.jpg - Lupin the Third: Mine Fujiko to Iu Onna - - - 4 - 13731 - Bones - http://cdn.myanimelist.net/images/anime/7/38107.jpg - Eureka Seven: New Order - - - 1 - 11761 - Gainax, TV Tokyo, Lantis, Media Factory, Sentai Filmworks, Asahi Production, Hakoniwa Academy Student Council - http://cdn.myanimelist.net/images/anime/13/37947.jpg - Medaka Box - - - 1 - 12291 - AIC, Pony Canyon, TBS, DAX Production, Sentai Filmworks, Acchi Kocchi Production Committee, BS-TBS, Jumondo, Hobunsha - http://cdn.myanimelist.net/images/anime/5/46489.jpg - Acchi Kocchi - - - 1 - 12611 - TV Tokyo, Brains Base, NAS, The Right Stuf International, Sengoku Collection Production Committee - http://cdn.myanimelist.net/images/anime/12/35329.jpg - Sengoku Collection - - - 1 - 12119 - Sunrise, Aniplex, Lantis, Mainichi Broadcasting, Natsuiro Kiseki Production Partners - http://cdn.myanimelist.net/images/anime/5/53913.jpg - Natsuiro Kiseki - - - 4 - 18531 - AIC - http://cdn.myanimelist.net/images/anime/9/49687.jpg - Acchi Kocchi Youchien - - - 1 - 13431 - DLE - http://cdn.myanimelist.net/images/anime/8/37613.jpg - Himitsukessha Taka no Tsume NEO - - - 5 - 13433 - - http://cdn.myanimelist.net/images/anime/13/37615.jpg - himitsukesshatakanotsume.jp - - - 1 - 11499 - Studio Deen, FUNimation Entertainment, Lantis, Pony Canyon, TBS, Kodansha, BS-TBS, Sankarea Production Committee - http://cdn.myanimelist.net/images/anime/13/39089.jpg - Sankarea - - - 5 - 12317 - Xebec, DAX Production, Nippon Columbia, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/13/35121.jpg - Upotte!! - - - 1 - 12863 - Nomad, Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/8/36633.jpg - Pretty Rhythm: Dear My Future - - - 1 - 13143 - - http://cdn.myanimelist.net/images/anime/3/36805.jpg - Baku Tech! Bakugan - - - 1 - 13465 - - http://cdn.myanimelist.net/images/anime/4/37611.jpg - Little Charo: Touhoku-hen - - - 1 - 12963 - TV Tokyo, Studio Comet - http://cdn.myanimelist.net/images/anime/12/37643.jpg - Jewelpet Kira Deco! - - - 1 - 11759 - Sunrise, Genco, Viz Media, Rakuonsha, Warner Bros., flying DOG, ASCII Media Works - http://cdn.myanimelist.net/images/anime/8/38155.jpg - Accel World - - - 3 - 12025 - Xebec, AIC - http://cdn.myanimelist.net/images/anime/3/45929.jpg - Uchuu Senkan Yamato 2199 Movie 1: Harukanaru Tabitachi - - - 2 - 12029 - Xebec, AIC - http://cdn.myanimelist.net/images/anime/2/36607.jpg - Uchuu Senkan Yamato 2199 - - - 1 - 13145 - TV Tokyo, Dentsu, Sotsu Agency, TMS Entertainment - http://cdn.myanimelist.net/images/anime/13/41627.jpg - Cardfight!! Vanguard: Asia Circuit-hen - - - 1 - 13231 - - http://cdn.myanimelist.net/images/anime/5/37159.jpg - Metal Fight Beyblade Zero G - - - 1 - 12467 - Starchild Records, Kodansha, Hoods Entertainment, Sentai Filmworks, Yomiuri Advertising - http://cdn.myanimelist.net/images/anime/6/35977.jpg - Nazo no Kanojo X - - - 1 - 12753 - Sunrise, NHK, NHK Enterprises, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/11/39033.jpg - Phi Brain: Kami no Puzzle 2nd Season - - - 1 - 11741 - Aniplex, ufotable, Nitroplus, Aniplex of America, seikaisha, Notes - http://cdn.myanimelist.net/images/anime/8/41125.jpg - Fate/Zero 2nd Season - - - 1 - 11771 - Production I.G, Bandai Visual, Lantis, NAS, Mainichi Broadcasting - http://cdn.myanimelist.net/images/anime/9/38167.jpg - Kuroko no Basket - - - 5 - 14077 - Production I.G - http://cdn.myanimelist.net/images/anime/7/38845.jpg - Blood-C: None-None Gekijou - - - 1 - 10884 - TV Tokyo, Square Enix, Lantis, Pony Canyon, Studio Gokumi, Saki Achiga-hen Production Committee - http://cdn.myanimelist.net/images/anime/3/37917.jpg - Saki: Achiga-hen - Episode of Side-A - - - 1 - 12445 - Media Factory, Silver Link, Sentai Filmworks, Tasogare Otome×Amnesia Production Partners - http://cdn.myanimelist.net/images/anime/5/50443.jpg - Tasogare Otome x Amnesia - - - 1 - 11785 - TV Tokyo, Xebec, The Klock Worx, Cospa, SoftBank Creative Corp., Studio Mausu - http://cdn.myanimelist.net/images/anime/6/49081.jpg - Haiyore! Nyaruko-san - - - 1 - 12413 - Geneon Universal Entertainment, Shogakukan Productions, FUNimation Entertainment, White Fox, Jormungand Production Partners - http://cdn.myanimelist.net/images/anime/8/38241.jpg - Jormungand - - - 4 - 14095 - - http://cdn.myanimelist.net/images/anime/11/38885.jpg - Kamen Rider Fourze X Crayon Shin-chan - - - 1 - 12367 - Production I.G, Avex Entertainment, Sega, DAX Production, Sentai Filmworks, Volks - http://cdn.myanimelist.net/images/anime/5/38329.jpg - Shining Hearts: Shiawase no Pan - - - 1 - 12883 - Aniplex, Dentsu, A-1 Pictures, Fuji TV, Sentai Filmworks, tsuritama partners, Sakura Create - http://cdn.myanimelist.net/images/anime/4/36185.jpg - Tsuritama - - - 1 - 12471 - Bones, Bandai Visual, FUNimation Entertainment, Mainichi Broadcasting, Project Eureka AO, Sony Music Entertainment - http://cdn.myanimelist.net/images/anime/11/36313.jpg - Eureka Seven AO - - - 1 - 12531 - Dentsu, Fuji TV, Tezuka Productions, Toho Company, DAX Production, Sentai Filmworks, MAPPA, Sony Music Entertainment - http://cdn.myanimelist.net/images/anime/4/36007.jpg - Sakamichi no Apollon - - - 2 - 13837 - Shogakukan Productions, Tokyo Movie Shinsha, Toho Company - http://cdn.myanimelist.net/images/anime/13/38385.jpg - Detective Conan Magic File 6: Fantasista Flower - - - 3 - 12117 - TMS Entertainment - http://cdn.myanimelist.net/images/anime/8/37511.jpg - Detective Conan Movie 16: The Eleventh Striker - - - 3 - 12499 - Shin-Ei Animation - http://cdn.myanimelist.net/images/anime/11/35099.jpg - Crayon Shin-chan Movie 20: Arashi wo Yobu! Ora to Uchuu no Princess - - - 1 - 13261 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/6/38255.jpg - Inazuma Eleven Go: Chrono Stone - - - 4 - 13927 - Sunrise - http://cdn.myanimelist.net/images/anime/3/38605.jpg - Sacred Seven: Shirogane no Tsubasa Picture Drama - - - 4 - 13983 - DLE - http://cdn.myanimelist.net/images/anime/3/38713.jpg - Thermae Romae: Kodai Romajin ga Uchuu e - - - 4 - 12727 - DLE - http://cdn.myanimelist.net/images/anime/2/35741.jpg - Thermae Romae Specials - - - 4 - 13245 - Production I.G, NAS, M.S.C - http://cdn.myanimelist.net/images/anime/7/39891.jpg - New Prince of Tennis Specials - - - 4 - 20205 - Toei Animation - http://cdn.myanimelist.net/images/anime/7/53781.jpg - Yama ni Kagayaku: Guide-ken Heiji Gou - - - 1 - 12189 - Kyoto Animation, Lantis, Kadokawa Shoten, The Klock Worx, chara-ani.com, Animation Do - http://cdn.myanimelist.net/images/anime/13/50521.jpg - Hyouka - - - 4 - 12505 - FUNimation Entertainment, Silver Link - http://cdn.myanimelist.net/images/anime/5/35169.jpg - C³ Special - - - 4 - 18003 - DLE - http://cdn.myanimelist.net/images/anime/8/47881.jpg - Thermae Romae: Thermae Romae x TOTO Collaboration - - - 1 - 12149 - Satelight, Starchild Records, Magic Capsule, Sentai Filmworks, AKB0048 Production Committee, GANSIS - http://cdn.myanimelist.net/images/anime/5/38921.jpg - AKB0048 First Stage - - - 4 - 17066 - Studio Pierrot, TV Tokyo, Avex Entertainment - http://cdn.myanimelist.net/images/anime/3/45650.jpg - Shirokuma Cafe: Golden Week Special - Shirokuma Cafe Selection - - - 4 - 11793 - TYO Animations - http://cdn.myanimelist.net/images/anime/4/32969.jpg - Tamayura: Hitotose - Attakai Kaze no Omoide Nanode - - - 3 - 13821 - - http://cdn.myanimelist.net/images/anime/12/38503.jpg - Kamiusagi Rope tsuka, Natsuyasumi Rasuichi tte Maji ssuka!? - - - 1 - 18845 - TV Asahi, Shin-Ei Animation - http://cdn.myanimelist.net/images/anime/9/50285.jpg - Ninja Hattori-kun (2012) - - - 5 - 14123 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/9/38953.jpg - Pokemon Black and White 2: Introduction Movie - - - 3 - 6219 - Toei Animation - http://cdn.myanimelist.net/images/anime/13/37541.jpg - Nijiiro Hotaru: Eien no Natsuyasumi - - - 4 - 17813 - Kyoto Animation, Lantis, Rakuonsha, Animation Do - http://cdn.myanimelist.net/images/anime/6/47443.jpg - Kyoto Animation: Megane-hen - - - 5 - 18841 - Studio 4°C - http://cdn.myanimelist.net/images/anime/8/50253.jpg - PES: Peace Eco Smile - Drive your Heart - - - 2 - 13839 - TMS Entertainment - http://cdn.myanimelist.net/images/anime/4/38387.jpg - Detective Conan OVA 12: The Miracle of Excalibur - - - 2 - 13103 - Arplants - http://cdn.myanimelist.net/images/anime/7/36769.jpg - Ponta to Ensoku - - - 5 - 13427 - Studio 4°C - http://cdn.myanimelist.net/images/anime/6/39245.jpg - PES: Peace Eco Smile - - - 4 - 19105 - - http://cdn.myanimelist.net/images/anime/4/51233.jpg - Fever Macross Pachinko Music Clips - - - 2 - 12501 - Frontier Works - http://cdn.myanimelist.net/images/anime/11/39157.jpg - Ai Mai! Moe Can Change! - - - 4 - 14117 - Production I.G - http://cdn.myanimelist.net/images/anime/11/39001.jpg - Blood-C: Special Edition - - - 2 - 11701 - P.A. Works - http://cdn.myanimelist.net/images/anime/9/42051.jpg - Another: The Other - Inga - - - 5 - 16606 - - http://cdn.myanimelist.net/images/anime/6/44756.jpg - C.L.A.Y. - - - 2 - 12695 - PrimeTime - http://cdn.myanimelist.net/images/anime/2/38305.jpg - Tight-rope - + + + + Spring 2012 + 1380310542 + + + 2 + 11209 + 6474 + Xebec, AIC, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/7/36929.jpg + Maken-Ki! OVA + + + 5 + 12237 + 6692 + Marvy Jack + http://cdn.myanimelist.net/images/anime/12/39121.jpg + Koi-ken!: Watashitachi Anime ni Nacchatta! + + + 3 + 11053 + 6447 + Tokyo Movie Shinsha + http://cdn.myanimelist.net/images/anime/6/49775.jpg + Doraemon: Nobita and the Miracle Island - Animal Adventure + + + 3 + 13169 + 6900 + Telecom Animation Film + http://cdn.myanimelist.net/images/anime/11/50405.jpg + Buta + + + 6 + 17949 + 7965 + Sony Music Entertainment, Carp Studio + http://cdn.myanimelist.net/images/anime/12/47807.jpg + The Everlasting Guilty Crown + + + 4 + 13263 + 6928 + Aniplex, ufotable, Aniplex of America + http://cdn.myanimelist.net/images/anime/6/54109.jpg + Fate/Zero: Onegai! Einzbern Soudanshitsu + + + 3 + 12223 + 6689 + Sunrise + http://cdn.myanimelist.net/images/anime/11/36835.jpg + Scryed Alteration II: Quan + + + 6 + 15673 + 7292 + + http://cdn.myanimelist.net/images/anime/2/42663.jpg + Tell Your World + + + 3 + 13171 + 6901 + Production I.G + http://cdn.myanimelist.net/images/anime/11/38877.jpg + Wasurenagumo + + + 5 + 13251 + 6923 + Production I.G, Studio 4°C, Shaft + http://cdn.myanimelist.net/images/anime/4/37343.jpg + Shin Hikari Shinwa: Palutena no Kagami + + + 4 + 13823 + 7037 + + http://cdn.myanimelist.net/images/anime/12/38367.jpg + Kamiusagi Rope Movie Episode 0 + + + 4 + 13825 + 7038 + + http://cdn.myanimelist.net/images/anime/5/38517.jpg + Kamiusagi Rope x AU Collaboration + + + 4 + 13435 + 6965 + + http://cdn.myanimelist.net/images/anime/6/37617.jpg + Taka no Tsume NEO Announcement Movie + + + 2 + 11813 + 6608 + Brains Base + http://cdn.myanimelist.net/images/anime/7/39893.jpg + Shijou Saikyou no Deshi Kenichi OVA + + + 2 + 11313 + 6492 + Tatsunoko Productions, Kodansha + http://cdn.myanimelist.net/images/anime/10/35843.jpg + Kimi no Iru Machi: Tasogare Kousaten + + + 1 + 12677 + 6788 + Gonzo, WOWOW, Pony Canyon, Planet, LandQ studios, Viki, Slowcurve + http://cdn.myanimelist.net/images/anime/4/35573.jpg + Ozma + + + 3 + 9751 + 5851 + AIC, NTT Docomo, 501st JOINT FIGHTER WING + http://cdn.myanimelist.net/images/anime/11/34907.jpg + Strike Witches the Movie + + + 3 + 12221 + 6688 + Toei Animation, Marvelous AQL + http://cdn.myanimelist.net/images/anime/8/39609.jpg + Precure All Stars New Stage: Mirai no Tomodachi + + + 2 + 13833 + 7042 + Kinema Citrus, Advance Syakujii + http://cdn.myanimelist.net/images/anime/7/38377.jpg + Nagareboshi Lens + + + 2 + 13835 + 7043 + Kinema Citrus + http://cdn.myanimelist.net/images/anime/12/38379.jpg + Marimo no Hana: Saikyou Butouha Shougakusei Densetsu + + + 4 + 13497 + 6977 + Ashi Productions + http://cdn.myanimelist.net/images/anime/9/37685.jpg + Neko no Sumu Shima + + + 4 + 13827 + 7039 + + http://cdn.myanimelist.net/images/anime/7/38519.jpg + Kamiusagi Rope x Panasonic Collaboration + + + 5 + 13359 + 6945 + + http://cdn.myanimelist.net/images/anime/8/37547.jpg + PistStar + + + 4 + 13173 + 6902 + The Answer Studio + http://cdn.myanimelist.net/images/anime/9/40249.jpg + Puka Puka Juju + + + 4 + 13357 + 6944 + Genco, FUNimation Entertainment, Lantis, TNK + http://cdn.myanimelist.net/images/anime/10/38449.jpg + High School DxD Specials + + + 2 + 10936 + 6414 + AIC Plus+, NIS America, Inc. + http://cdn.myanimelist.net/images/anime/4/30178.jpg + Nekogami Yaoyorozu: Ohanami Ghostbusters + + + 5 + 13563 + 6995 + + http://cdn.myanimelist.net/images/anime/13/37765.jpg + The Four Seasons + + + 2 + 11005 + 6437 + Cammot + http://cdn.myanimelist.net/images/anime/10/41841.jpg + Holy Knight + + + 2 + 13479 + 6975 + Milky Cartoon + http://cdn.myanimelist.net/images/anime/4/37631.jpg + Uchuu Kyoudai: Apo's Dream + + + 4 + 13495 + 6976 + Production I.G + http://cdn.myanimelist.net/images/anime/12/47557.jpg + Tokyo Disney Resort: Yume ga Kanau Basho + + + 5 + 16604 + 7471 + + http://cdn.myanimelist.net/images/anime/11/44752.jpg + Pinky + + + 4 + 14583 + 7139 + Production I.G, Bandai Visual, Xebec, flying DOG, Dwango + http://cdn.myanimelist.net/images/anime/13/39747.jpg + Rinne no Lagrange Specials + + + 3 + 13253 + 6924 + Ordet + http://cdn.myanimelist.net/images/anime/11/37105.jpg + Blossom + + + 4 + 13183 + 6906 + Aniplex, ufotable, Nitroplus, Aniplex of America + http://cdn.myanimelist.net/images/anime/6/37081.jpg + Fate/Zero Remix + + + 1 + 12875 + 6832 + NHK, NAS, NHK Enterprises, TYO Animations + http://cdn.myanimelist.net/images/anime/8/36117.jpg + Ginga e Kickoff!! + + + 4 + 13175 + 6903 + Shirogumi + http://cdn.myanimelist.net/images/anime/4/50407.jpg + Shiranpuri + + + 4 + 13247 + 6921 + Tokyo Movie Shinsha + http://cdn.myanimelist.net/images/anime/2/46379.jpg + Lupin III: Lupin Ikka Seizoroi + + + 4 + 13249 + 6922 + TMS Entertainment + http://cdn.myanimelist.net/images/anime/3/50251.jpg + Lupin VIII + + + 4 + 17811 + 7738 + Kyoto Animation, Lantis, Rakuonsha + http://cdn.myanimelist.net/images/anime/10/47441.jpg + Kyoto Animation: Hassou-hen + + + 1 + 13141 + 6891 + DAX Production, Dream Creation, Hotline + http://cdn.myanimelist.net/images/anime/7/38283.jpg + Shiba Inuko-san + + + 4 + 13669 + 7009 + + http://cdn.myanimelist.net/images/anime/4/37931.jpg + Owari no Chronicle + + + 1 + 13163 + 6897 + TV Tokyo, Tomason, Sony Music Entertainment, Peter Pan Creation + http://cdn.myanimelist.net/images/anime/4/36869.jpg + Furusato Saisei: Nihon no Mukashi Banashi + + + 4 + 13677 + 7012 + + http://cdn.myanimelist.net/images/anime/6/37993.jpg + Ao no Exorcist (Movie) Special + + + 1 + 12929 + 6850 + Toei Animation + http://cdn.myanimelist.net/images/anime/12/43597.jpg + Saint Seiya Omega + + + 1 + 12431 + 6729 + Aniplex, Dentsu, A-1 Pictures, YTV, Sentai Filmworks, Japan Aerospace Exploration Agency, Trinity Sound + http://cdn.myanimelist.net/images/anime/7/37573.jpg + Uchuu Kyoudai + + + 1 + 12461 + 6739 + Bandai Visual, Studio Deen, Lantis, YTV, DAX Production, Sentai Filmworks, Enterbrain, Hiiro No Kakera Production Committee + http://cdn.myanimelist.net/images/anime/3/36925.jpg + Hiiro no Kakera + + + 1 + 13165 + 6898 + + http://cdn.myanimelist.net/images/anime/11/36873.jpg + Paboo & Mojies + + + 1 + 13463 + 6972 + + http://cdn.myanimelist.net/images/anime/8/37609.jpg + Panda no Taputapu + + + 1 + 7867 + 5147 + Kodansha, Daewon Media + http://cdn.myanimelist.net/images/anime/6/36931.jpg + Gon + + + 1 + 18941 + + + http://cdn.myanimelist.net/images/anime/9/50737.jpg + Shimajirou no Wow! + + + 1 + 11837 + 6614 + TV Asahi, TMS Entertainment, Viz Media, Asatsu DK, YTV, Toho Company, NYAV Post, Sakura Create + http://cdn.myanimelist.net/images/anime/7/35529.jpg + Zetman + + + 1 + 11859 + 6620 + Arms, Genco, Media Factory, AT-X, Sentai Filmworks, Hobby Japan, NYAV Post + http://cdn.myanimelist.net/images/anime/3/35023.jpg + Queen's Blade: Rebellion + + + 1 + 13139 + 6890 + NHK + http://cdn.myanimelist.net/images/anime/2/36807.jpg + Gakkatsu! + + + 1 + 12123 + 6669 + DAX Production, Takeshobo, C2C, Right Gauge, Dwango Music Entertainment + http://cdn.myanimelist.net/images/anime/3/36705.jpg + Yurumates 3D + + + 4 + 12893 + 6839 + Sunrise + http://cdn.myanimelist.net/images/anime/8/38527.jpg + Danshi Koukousei no Nichijou Specials + + + 1 + 13455 + 6969 + Egg + http://cdn.myanimelist.net/images/anime/7/46983.jpg + Zumomo to Nupepe + + + 1 + 12979 + 6861 + Studio Pierrot, TV Tokyo, Viz Media + http://cdn.myanimelist.net/images/anime/13/36475.jpg + Rock Lee no Seishun Full-Power Ninden + + + 1 + 11739 + 6581 + J.C. Staff, TV Tokyo, Aniplex, Square Enix, NAS, Movic, Kimi To Boku Production Partners + http://cdn.myanimelist.net/images/anime/10/42107.jpg + Kimi to Boku. 2 + + + 1 + 13377 + 6951 + Takeshobo, Seven + http://cdn.myanimelist.net/images/anime/11/38901.jpg + Recorder to Randoseru Re♪ + + + 1 + 13159 + 6895 + NHK, Shin-Ei Animation + http://cdn.myanimelist.net/images/anime/6/36853.jpg + Kuromajo-san ga Tooru!! + + + 4 + 12669 + 6785 + AIC + http://cdn.myanimelist.net/images/anime/9/35567.jpg + Amagami SS+ Plus Specials + + + 1 + 13459 + 6971 + + http://cdn.myanimelist.net/images/anime/13/48753.jpg + Ribbon-chan + + + 1 + 13029 + 6868 + Sparky Animation + http://cdn.myanimelist.net/images/anime/4/36603.jpg + Arashi no Yoru ni: Himitsu no Tomodachi + + + 1 + 12815 + 6821 + Studio Pierrot, TV Tokyo, Avex Entertainment + http://cdn.myanimelist.net/images/anime/10/36803.jpg + Shirokuma Cafe + + + 1 + 10790 + 6347 + Studio Deen, FUNimation Entertainment, Kadokawa Shoten, AT-X, Kadokawa Pictures Japan, The Klock Worx, flying DOG + http://cdn.myanimelist.net/images/anime/4/37451.jpg + Kore wa Zombie Desu ka? of the Dead + + + 1 + 13203 + 6910 + TMS Entertainment, FUNimation Entertainment, Po10tial + http://cdn.myanimelist.net/images/anime/3/41291.jpg + Lupin the Third: Mine Fujiko to Iu Onna + + + 4 + 13731 + 7020 + Bones + http://cdn.myanimelist.net/images/anime/7/38107.jpg + Eureka Seven: New Order + + + 1 + 11761 + 6591 + Gainax, TV Tokyo, Lantis, Media Factory, Sentai Filmworks, Asahi Production, Hakoniwa Academy Student Council + http://cdn.myanimelist.net/images/anime/13/37947.jpg + Medaka Box + + + 1 + 12291 + 6701 + AIC, Pony Canyon, TBS, DAX Production, Sentai Filmworks, Acchi Kocchi Production Committee, BS-TBS, Jumondo, Hobunsha + http://cdn.myanimelist.net/images/anime/5/46489.jpg + Acchi Kocchi + + + 1 + 12611 + 6774 + TV Tokyo, Brains Base, NAS, The Right Stuf International, Sengoku Collection Production Committee + http://cdn.myanimelist.net/images/anime/12/35329.jpg + Sengoku Collection + + + 1 + 12119 + 6667 + Sunrise, Aniplex, Lantis, Mainichi Broadcasting, Natsuiro Kiseki Production Partners + http://cdn.myanimelist.net/images/anime/5/53913.jpg + Natsuiro Kiseki + + + 4 + 18531 + 7750 + AIC + http://cdn.myanimelist.net/images/anime/9/49687.jpg + Acchi Kocchi Youchien + + + 1 + 13431 + 6963 + DLE + http://cdn.myanimelist.net/images/anime/8/37613.jpg + Himitsukessha Taka no Tsume NEO + + + 5 + 13433 + 6964 + + http://cdn.myanimelist.net/images/anime/13/37615.jpg + himitsukesshatakanotsume.jp + + + 1 + 11499 + 6521 + Studio Deen, FUNimation Entertainment, Lantis, Pony Canyon, TBS, Kodansha, BS-TBS, Sankarea Production Committee + http://cdn.myanimelist.net/images/anime/13/39089.jpg + Sankarea + + + 5 + 12317 + 6704 + Xebec, DAX Production, Nippon Columbia, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/13/35121.jpg + Upotte!! + + + 1 + 12863 + 6829 + Nomad, Tatsunoko Productions + http://cdn.myanimelist.net/images/anime/8/36633.jpg + Pretty Rhythm: Dear My Future + + + 1 + 13143 + 6892 + + http://cdn.myanimelist.net/images/anime/3/36805.jpg + Baku Tech! Bakugan + + + 1 + 13465 + 6973 + + http://cdn.myanimelist.net/images/anime/4/37611.jpg + Little Charo: Touhoku-hen + + + 1 + 12963 + 6857 + TV Tokyo, Studio Comet + http://cdn.myanimelist.net/images/anime/12/37643.jpg + Jewelpet Kira Deco! + + + 1 + 11759 + 6590 + Sunrise, Genco, Viz Media, Rakuonsha, Warner Bros., flying DOG, ASCII Media Works + http://cdn.myanimelist.net/images/anime/8/38155.jpg + Accel World + + + 3 + 12025 + + Xebec, AIC + http://cdn.myanimelist.net/images/anime/3/45929.jpg + Uchuu Senkan Yamato 2199 Movie 1: Harukanaru Tabitachi + + + 2 + 12029 + 6646 + Xebec, AIC + http://cdn.myanimelist.net/images/anime/2/36607.jpg + Uchuu Senkan Yamato 2199 + + + 1 + 13145 + 6893 + TV Tokyo, Dentsu, Sotsu Agency, TMS Entertainment + http://cdn.myanimelist.net/images/anime/13/41627.jpg + Cardfight!! Vanguard: Asia Circuit-hen + + + 1 + 13231 + 6917 + + http://cdn.myanimelist.net/images/anime/5/37159.jpg + Metal Fight Beyblade Zero G + + + 1 + 12467 + 6740 + Starchild Records, Kodansha, Hoods Entertainment, Sentai Filmworks, Yomiuri Advertising + http://cdn.myanimelist.net/images/anime/6/35977.jpg + Nazo no Kanojo X + + + 1 + 12753 + 6803 + Sunrise, NHK, NHK Enterprises, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/11/39033.jpg + Phi Brain: Kami no Puzzle 2nd Season + + + 1 + 11741 + 7658 + Aniplex, ufotable, Nitroplus, Aniplex of America, seikaisha, Notes + http://cdn.myanimelist.net/images/anime/8/41125.jpg + Fate/Zero 2nd Season + + + 1 + 11771 + 6595 + Production I.G, Bandai Visual, Lantis, NAS, Mainichi Broadcasting + http://cdn.myanimelist.net/images/anime/9/38167.jpg + Kuroko no Basket + + + 5 + 14077 + 7079 + Production I.G + http://cdn.myanimelist.net/images/anime/7/38845.jpg + Blood-C: None-None Gekijou + + + 1 + 10884 + 6392 + TV Tokyo, Square Enix, Lantis, Pony Canyon, Studio Gokumi, Saki Achiga-hen Production Committee + http://cdn.myanimelist.net/images/anime/3/37917.jpg + Saki: Achiga-hen - Episode of Side-A + + + 1 + 12445 + 6734 + Media Factory, Silver Link, Sentai Filmworks, Tasogare Otome×Amnesia Production Partners + http://cdn.myanimelist.net/images/anime/5/50443.jpg + Tasogare Otome x Amnesia + + + 1 + 11785 + 6599 + TV Tokyo, Xebec, The Klock Worx, Cospa, SoftBank Creative Corp., Studio Mausu + http://cdn.myanimelist.net/images/anime/6/49081.jpg + Haiyore! Nyaruko-san + + + 1 + 12413 + 6726 + Geneon Universal Entertainment, Shogakukan Productions, FUNimation Entertainment, White Fox, Jormungand Production Partners + http://cdn.myanimelist.net/images/anime/8/38241.jpg + Jormungand + + + 4 + 14095 + 7081 + + http://cdn.myanimelist.net/images/anime/11/38885.jpg + Kamen Rider Fourze X Crayon Shin-chan + + + 1 + 12367 + 6715 + Production I.G, Avex Entertainment, Sega, DAX Production, Sentai Filmworks, Volks + http://cdn.myanimelist.net/images/anime/5/38329.jpg + Shining Hearts: Shiawase no Pan + + + 1 + 12883 + 6836 + Aniplex, Dentsu, A-1 Pictures, Fuji TV, Sentai Filmworks, tsuritama partners, Sakura Create + http://cdn.myanimelist.net/images/anime/4/36185.jpg + Tsuritama + + + 1 + 12471 + 6741 + Bones, Bandai Visual, FUNimation Entertainment, Mainichi Broadcasting, Project Eureka AO, Sony Music Entertainment + http://cdn.myanimelist.net/images/anime/11/36313.jpg + Eureka Seven AO + + + 1 + 12531 + 6760 + Dentsu, Fuji TV, Tezuka Productions, Toho Company, DAX Production, Sentai Filmworks, MAPPA, Sony Music Entertainment + http://cdn.myanimelist.net/images/anime/4/36007.jpg + Sakamichi no Apollon + + + 2 + 13837 + 7044 + Shogakukan Productions, Tokyo Movie Shinsha, Toho Company + http://cdn.myanimelist.net/images/anime/13/38385.jpg + Detective Conan Magic File 6: Fantasista Flower + + + 3 + 12117 + 6666 + TMS Entertainment + http://cdn.myanimelist.net/images/anime/8/37511.jpg + Detective Conan Movie 16: The Eleventh Striker + + + 3 + 12499 + 6748 + Shin-Ei Animation + http://cdn.myanimelist.net/images/anime/11/35099.jpg + Crayon Shin-chan Movie 20: Arashi wo Yobu! Ora to Uchuu no Princess + + + 1 + 13261 + 6927 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/6/38255.jpg + Inazuma Eleven Go: Chrono Stone + + + 4 + 13927 + 7053 + Sunrise + http://cdn.myanimelist.net/images/anime/3/38605.jpg + Sacred Seven: Shirogane no Tsubasa Picture Drama + + + 4 + 13983 + 7065 + DLE + http://cdn.myanimelist.net/images/anime/3/38713.jpg + Thermae Romae: Kodai Romajin ga Uchuu e + + + 4 + 12727 + 6800 + DLE + http://cdn.myanimelist.net/images/anime/2/35741.jpg + Thermae Romae Specials + + + 4 + 13245 + 6920 + Production I.G, NAS, M.S.C + http://cdn.myanimelist.net/images/anime/7/39891.jpg + New Prince of Tennis Specials + + + 4 + 20205 + + Toei Animation + http://cdn.myanimelist.net/images/anime/7/53781.jpg + Yama ni Kagayaku: Guide-ken Heiji Gou + + + 1 + 12189 + 6686 + Kyoto Animation, Lantis, Kadokawa Shoten, The Klock Worx, chara-ani.com, Animation Do + http://cdn.myanimelist.net/images/anime/13/50521.jpg + Hyouka + + + 4 + 12505 + 6751 + FUNimation Entertainment, Silver Link + http://cdn.myanimelist.net/images/anime/5/35169.jpg + C³ Special + + + 4 + 18003 + + DLE + http://cdn.myanimelist.net/images/anime/8/47881.jpg + Thermae Romae: Thermae Romae x TOTO Collaboration + + + 1 + 12149 + 6678 + Satelight, Starchild Records, Magic Capsule, Sentai Filmworks, AKB0048 Production Committee, GANSIS + http://cdn.myanimelist.net/images/anime/5/38921.jpg + AKB0048 First Stage + + + 4 + 17066 + 7571 + Studio Pierrot, TV Tokyo, Avex Entertainment + http://cdn.myanimelist.net/images/anime/3/45650.jpg + Shirokuma Cafe: Golden Week Special - Shirokuma Cafe Selection + + + 4 + 11793 + 6603 + TYO Animations + http://cdn.myanimelist.net/images/anime/4/32969.jpg + Tamayura: Hitotose - Attakai Kaze no Omoide Nanode + + + 3 + 13821 + 7036 + + http://cdn.myanimelist.net/images/anime/12/38503.jpg + Kamiusagi Rope tsuka, Natsuyasumi Rasuichi tte Maji ssuka!? + + + 1 + 18845 + + TV Asahi, Shin-Ei Animation + http://cdn.myanimelist.net/images/anime/9/50285.jpg + Ninja Hattori-kun (2012) + + + 5 + 14123 + 7083 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/9/38953.jpg + Pokemon Black and White 2: Introduction Movie + + + 3 + 6219 + 4481 + Toei Animation + http://cdn.myanimelist.net/images/anime/13/37541.jpg + Nijiiro Hotaru: Eien no Natsuyasumi + + + 4 + 17813 + 7741 + Kyoto Animation, Lantis, Rakuonsha, Animation Do + http://cdn.myanimelist.net/images/anime/6/47443.jpg + Kyoto Animation: Megane-hen + + + 5 + 18841 + + Studio 4°C + http://cdn.myanimelist.net/images/anime/8/50253.jpg + PES: Peace Eco Smile - Drive your Heart + + + 2 + 13839 + 7045 + TMS Entertainment + http://cdn.myanimelist.net/images/anime/4/38387.jpg + Detective Conan OVA 12: The Miracle of Excalibur + + + 2 + 13103 + 6883 + Arplants + http://cdn.myanimelist.net/images/anime/7/36769.jpg + Ponta to Ensoku + + + 5 + 13427 + 6961 + Studio 4°C + http://cdn.myanimelist.net/images/anime/6/39245.jpg + PES: Peace Eco Smile + + + 4 + 19105 + + + http://cdn.myanimelist.net/images/anime/4/51233.jpg + Fever Macross Pachinko Music Clips + + + 2 + 12501 + 6749 + Frontier Works + http://cdn.myanimelist.net/images/anime/11/39157.jpg + Ai Mai! Moe Can Change! + + + 4 + 14117 + 7082 + Production I.G + http://cdn.myanimelist.net/images/anime/11/39001.jpg + Blood-C: Special Edition + + + 2 + 11701 + 6569 + P.A. Works + http://cdn.myanimelist.net/images/anime/9/42051.jpg + Another: The Other - Inga + + + 5 + 16606 + 7472 + + http://cdn.myanimelist.net/images/anime/6/44756.jpg + C.L.A.Y. + + + 2 + 12695 + 6793 + PrimeTime + http://cdn.myanimelist.net/images/anime/2/38305.jpg + Tight-rope + \ No newline at end of file diff --git a/data/db/season/2012_summer.xml b/data/db/season/2012_summer.xml index b5b3107c9..a24364b4f 100644 --- a/data/db/season/2012_summer.xml +++ b/data/db/season/2012_summer.xml @@ -1,728 +1,831 @@ - - - - Summer 2012 - 1380319211 - - - 3 - 10681 - Production I.G, Aniplex, Dentsu, FUNimation Entertainment, Shochiku, Kadokawa Shoten, Mainichi Broadcasting, Dwango - http://cdn.myanimelist.net/images/anime/9/34495.jpg - Blood-C: The Last Dark - - - 4 - 14317 - Production I.G - http://cdn.myanimelist.net/images/anime/10/39329.jpg - Computer Kakumei: Saikyou x Saisoku no Zunou Tanjou - - - 3 - 14853 - Sunrise - http://cdn.myanimelist.net/images/anime/12/53171.jpg - Tsukumo - - - 3 - 13639 - Sunrise - http://cdn.myanimelist.net/images/anime/10/53165.jpg - Hi no Youjin - - - 1 - 12031 - Studio Pierrot, FUNimation Entertainment, NHK - http://cdn.myanimelist.net/images/anime/13/39511.jpg - Kingdom - - - 6 - 14359 - - http://cdn.myanimelist.net/images/anime/11/39405.jpg - Vocaloid China Project Anime PV - - - 2 - 13055 - Studio Deen - http://cdn.myanimelist.net/images/anime/13/44916.jpg - Sankarea OVA - - - 3 - 14267 - AIC A.S.T.A., Index - http://cdn.myanimelist.net/images/anime/3/50415.jpg - Persona 4 The Animation: The Factor of Hope - - - 2 - 14479 - AIC - http://cdn.myanimelist.net/images/anime/6/39537.jpg - Mahou Tsukai Nara Miso wo Kue! - - - 3 - 11001 - Production I.G - http://cdn.myanimelist.net/images/anime/9/39273.jpg - Toshokan Sensou: Kakumei no Tsubasa - - - 4 - 11889 - Aniplex, A-1 Pictures - http://cdn.myanimelist.net/images/anime/8/33267.jpg - The iDOLM@STER: 765 Pro to Iu Monogatari - - - 1 - 14093 - TV Tokyo, Oriental Light and Magic, MediaNet, Half H.P Studio, Studio Jack - http://cdn.myanimelist.net/images/anime/3/39007.jpg - Pokemon Best Wishes! Season 2 - - - 5 - 14537 - - http://cdn.myanimelist.net/images/anime/3/39633.jpg - Cinnamon no Parade - - - 4 - 20547 - Gainax - http://cdn.myanimelist.net/images/anime/5/54555.jpg - Top wo Nerae 2! Diebuster! Science Lesson - - - 2 - 12581 - GoHands - http://cdn.myanimelist.net/images/anime/7/40395.jpg - Asa Made Jugyou Chu! - - - 3 - 12113 - Studio 4°C, Viz Media, NYAV Post, Yahoo! Japan - http://cdn.myanimelist.net/images/anime/12/37193.jpg - Berserk: Ougon Jidaihen II - Doldrey Kouryaku - - - 2 - 13727 - Production I.G, Xebec - http://cdn.myanimelist.net/images/anime/10/38259.jpg - Rinne no Lagrange: Kamogawa Days - - - 3 - 20469 - Himeyuri Alumnae Incorporated Foundation, ASIA Documentary Productions - http://cdn.myanimelist.net/images/anime/5/54359.jpg - Himeyuri - - - 3 - 12355 - Madhouse Studios, VAP, Dentsu, Yomiuri Telecasting Corporation, FUNimation Entertainment, Kadokawa Shoten, Toho Company, Studio Chizu - http://cdn.myanimelist.net/images/anime/9/35721.jpg - Ookami Kodomo no Ame to Yuki - - - 4 - 13855 - Arms, Genco, Media Factory, Sentai Filmworks, Hobby Japan - http://cdn.myanimelist.net/images/anime/7/38419.jpg - Queen's Blade: Rebellion Specials - - - 4 - 15219 - - http://cdn.myanimelist.net/images/anime/10/42325.jpg - .hack//Versus: The Thanatos Report - - - 3 - 12507 - Xebec, AIC - http://cdn.myanimelist.net/images/anime/6/45927.jpg - Uchuu Senkan Yamato 2199 Movie 2: Taiyou-ken no Shitou - - - 4 - 17791 - Kyoto Animation, Lantis, Rakuonsha - http://cdn.myanimelist.net/images/anime/13/47365.jpg - Kyoto Animation: Ikitaku Naru Omise-hen - - - 2 - 14039 - G-mode - http://cdn.myanimelist.net/images/anime/7/38787.jpg - Katayoku no Khronos Gear - - - 1 - 13333 - Bandai Visual, Dentsu, Sotsu Agency, Lantis, P.A. Works, Pony Canyon, Sentai Filmworks, Showgate - http://cdn.myanimelist.net/images/anime/6/50423.jpg - Tari Tari - - - 4 - 14675 - Production I.G, Xebec - http://cdn.myanimelist.net/images/anime/7/39929.jpg - Rinne no Lagrange: Kamogawa Memoria - - - 1 - 12967 - J.C. Staff, Frontier Works, Movic, Sentai Filmworks, Warner Bros., Showgate - http://cdn.myanimelist.net/images/anime/9/39495.jpg - Arcana Famiglia - - - 1 - 11021 - TV Tokyo, Satelight, Avex Entertainment, AT-X, Magic Capsule, Ixtl - http://cdn.myanimelist.net/images/anime/3/44462.jpg - Muv-Luv Alternative: Total Eclipse - - - 1 - 10357 - Sotsu Agency, AIC A.S.T.A., Lantis, Pony Canyon, Movic, Sentai Filmworks, Marvelous AQL - http://cdn.myanimelist.net/images/anime/4/45704.jpg - Jinrui wa Suitai Shimashita - - - 1 - 14277 - AT-X, Silver Link, Takeshobo, Dream Creation - http://cdn.myanimelist.net/images/anime/10/50537.jpg - Chitose Get You!! - - - 1 - 13349 - TYO Animations - http://cdn.myanimelist.net/images/anime/7/42007.jpg - Chouyaku Hyakuninisshu: Uta Koi. - - - 2 - 14935 - - http://cdn.myanimelist.net/images/anime/3/40543.jpg - Utae Meloetta: Rinka no Mi wo Sagase! - - - 1 - 14693 - DAX Production, Takeshobo, C2C, Right Gauge, Dwango Music Entertainment - http://cdn.myanimelist.net/images/anime/5/40001.jpg - Yurumates 3D Plus - - - 1 - 12403 - Dogakobo, Pony Canyon, DAX Production - http://cdn.myanimelist.net/images/anime/9/39071.jpg - Yuru Yuri♪♪ - - - 1 - 13535 - Sunrise, TV Tokyo, Dentsu, FUNimation Entertainment, Kodansha, Trinity Sound, Sony Music Entertainment, Studio Jack - http://cdn.myanimelist.net/images/anime/8/39333.jpg - Binbougami ga! - - - 1 - 12293 - Diomedea, Lantis, AT-X, Barnum Studio, Magic Capsule, Sentai Filmworks, Warner Bros., The Klock Worx - http://cdn.myanimelist.net/images/anime/4/37939.jpg - Campione!: Matsurowanu Kamigami to Kamigoroshi no Maou - - - 1 - 12549 - Geneon Universal Entertainment, Sotsu Agency, Feel, Lantis, AT-X, Sentai Filmworks, The Klock Worx - http://cdn.myanimelist.net/images/anime/6/38281.jpg - Dakara Boku wa, H ga Dekinai. - - - 1 - 13585 - Dogakobo, Fuji TV, Toho Company, Sentai Filmworks, Sony Music Entertainment - http://cdn.myanimelist.net/images/anime/11/38181.jpg - Natsuyuki Rendezvous - - - 1 - 13367 - Lantis, TBS, DAX Production, Sentai Filmworks, Studio Gokumi - http://cdn.myanimelist.net/images/anime/11/39587.jpg - Kono Naka ni Hitori, Imouto ga Iru! - - - 1 - 13409 - Aniplex, Dentsu, Telecom Animation Film, Kodansha, Fuji TV, Shirogumi - http://cdn.myanimelist.net/images/anime/11/39775.jpg - Moyashimon Returns - - - 1 - 13161 - Arms, Genco, FUNimation Entertainment, Lantis, Media Factory, AT-X, Showgate - http://cdn.myanimelist.net/images/anime/2/37577.jpg - Hagure Yuusha no Estetica - - - 1 - 12679 - J.C. Staff, Starchild Records, Mainichi Broadcasting, Kodansha - http://cdn.myanimelist.net/images/anime/8/48925.jpg - Joshiraku - - - 1 - 12175 - Aniplex, Lantis, Magic Capsule, Sentai Filmworks, AIC Build - http://cdn.myanimelist.net/images/anime/4/42015.jpg - Koi to Senkyo to Chocolate - - - 2 - 15077 - CoMix Wave - http://cdn.myanimelist.net/images/anime/11/40907.jpg - Tabisuru Nuigurumi: Traveling Daru - - - 1 - 11783 - Aniplex, Seven Arcs - http://cdn.myanimelist.net/images/anime/9/42105.jpg - Dog Days' - - - 3 - 14381 - - http://cdn.myanimelist.net/images/anime/3/39427.jpg - Sore Ike! Anpanman: Yomigaere Bananajima - - - 4 - 15927 - Lerche - http://cdn.myanimelist.net/images/anime/4/43321.jpg - Carnival Phantasm: HibiChika Special - - - 3 - 13215 - Tezuka Productions - http://cdn.myanimelist.net/images/anime/13/36995.jpg - Guskou Budori no Denki (2012) - - - 1 - 15069 - - http://cdn.myanimelist.net/images/anime/11/40909.jpg - Robin-kun to 100 nin no Otomodachi Season 2 - - - 1 - 14333 - TV Tokyo, Toei Animation, Dentsu - http://cdn.myanimelist.net/images/anime/3/39357.jpg - Tanken Driland - - - 1 - 11887 - Starchild Records, Silver Link, Sentai Filmworks, Enterbrain - http://cdn.myanimelist.net/images/anime/2/39665.jpg - Kokoro Connect - - - 2 - 13469 - Kyoto Animation - http://cdn.myanimelist.net/images/anime/6/50363.jpg - Hyouka: Motsubeki Mono wa - - - 1 - 12487 - Sunrise, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/6/38227.jpg - Kyoukaisenjou no Horizon II - - - 1 - 11757 - Aniplex, A-1 Pictures, Genco, DAX Production, Aniplex of America, ASCII Media Works - http://cdn.myanimelist.net/images/anime/11/39717.jpg - Sword Art Online - - - 1 - 12281 - Production I.G, Xebec, Viz Media - http://cdn.myanimelist.net/images/anime/4/39321.jpg - Rinne no Lagrange Season 2 - - - 1 - 13115 - Studio Deen, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/12/39163.jpg - Hakuouki Reimeiroku - - - 1 - 11933 - Madhouse Studios, Pony Canyon, AT-X, Studio Gokumi, The Klock Worx, SoftBank Creative Corp. - http://cdn.myanimelist.net/images/anime/11/39249.jpg - Oda Nobuna no Yabou - - - 4 - 12673 - Feel - http://cdn.myanimelist.net/images/anime/3/35569.jpg - Papa no Iukoto wo Kikinasai!: Pokkapoka - - - 3 - 12671 - TV Tokyo, Oriental Light and Magic, Shogakukan Productions, Toho Company, Studio Jack - http://cdn.myanimelist.net/images/anime/11/38311.jpg - Pokemon Best Wishes! Season 2: Kyurem vs. Seikenshi - - - 3 - 10153 - Seven Arcs - http://cdn.myanimelist.net/images/anime/13/35733.jpg - Mahou Shoujo Lyrical Nanoha: The Movie 2nd A's - - - 4 - 13799 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/3/49309.jpg - Pokemon: Meloetta no Kirakira Recital - - - 5 - 14073 - AIC, Sentai Filmworks, AMG MUSIC - http://cdn.myanimelist.net/images/anime/7/38889.jpg - Ebiten: Kouritsu Ebisugawa Koukou Tenmonbu - - - 6 - 17293 - Studio 4°C, Shirogumi - http://cdn.myanimelist.net/images/anime/4/46189.jpg - Love Like Aliens - - - 3 - 8888 - Sunrise, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/5/39295.jpg - Code Geass: Boukoku no Akito 1 - Yokuryuu wa Maiorita - - - 2 - 14627 - Takeshobo, Seven - http://cdn.myanimelist.net/images/anime/10/40081.jpg - Recorder to Randoseru - - - 6 - 20529 - Sony Music Entertainment - http://cdn.myanimelist.net/images/anime/9/54519.jpg - Harinezumi - - - 3 - 14629 - - http://cdn.myanimelist.net/images/anime/7/39815.jpg - Starship Troopers: Invasion - - - 3 - 17573 - A-1 Pictures, Digital Media Lab - http://cdn.myanimelist.net/images/anime/2/47201.jpg - Planetarium Uchuu Kyoudai: Itten no Hikari - - - 4 - 13859 - J.C. Staff, Viz Media - http://cdn.myanimelist.net/images/anime/10/38469.jpg - Accel World Specials - - - 4 - 16532 - - http://cdn.myanimelist.net/images/anime/6/44620.jpg - Beelzebub: Kaiketsu!! Beel-bo Meitantei Suiri - - - 4 - 13411 - Production I.G, Aniplex - http://cdn.myanimelist.net/images/anime/12/37525.jpg - Guilty Crown: Lost Christmas - - - 2 - 14631 - J.C. Staff - http://cdn.myanimelist.net/images/anime/2/39817.jpg - Dangerous Jiisan Ja - - - 4 - 15487 - Production I.G - http://cdn.myanimelist.net/images/anime/12/42751.jpg - Kuroko no Basket NG-shuu - - - 2 - 12685 - Sunrise - http://cdn.myanimelist.net/images/anime/7/36175.jpg - Code Geass: Nunnally in Wonderland - - - 4 - 15505 - Production I.G, Avex Entertainment, Sega, DAX Production, Volks - http://cdn.myanimelist.net/images/anime/13/42153.jpg - Shining Hearts: Shiawase no Pan Specials - - - 4 - 15769 - Production I.G, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/12/42789.jpg - Shining Hearts: Shiawase no Pan - Kokoro ga Todoita Picture Drama - - - 3 - 13667 - Studio Pierrot, TV Tokyo, Aniplex, Dentsu, Toho Company - http://cdn.myanimelist.net/images/anime/6/51863.jpg - Naruto: Shippuuden Movie 6 - Road to Ninja - - - 2 - 9988 - Silver Link - http://cdn.myanimelist.net/images/anime/12/37657.jpg - Otome wa Boku ni Koishiteru: Futari no Elder - - - 1 - 14563 - AIC Frontier - http://cdn.myanimelist.net/images/anime/4/39693.jpg - Maji de Otaku na English! Ribbon-chan: Eigo de Tatakau Mahou Shoujo - - - 2 - 13807 - Asread - http://cdn.myanimelist.net/images/anime/5/38331.jpg - Corpse Party: Missing Footage - - - 2 - 15211 - SynergySP - http://cdn.myanimelist.net/images/anime/10/42143.jpg - Nazotoki-hime wa Meitantei♥ - - - 5 - 15005 - Lantis, Half H.P Studio, Silver Link - http://cdn.myanimelist.net/images/anime/6/46553.jpg - Kyou no Asuka Show - - - 2 - 13283 - Dentsu, TYO Animations - http://cdn.myanimelist.net/images/anime/12/39661.jpg - One Off - - - 2 - 13267 - Diomedea - http://cdn.myanimelist.net/images/anime/4/39055.jpg - Shinryaku!! Ika Musume - - - 2 - 12879 - Gainax, TV Tokyo, Kadokawa Shoten, Kadokawa Pictures Japan, The Klock Worx - http://cdn.myanimelist.net/images/anime/13/46959.jpg - Dantalian no Shoka: Ibarahime - - - 3 - 14947 - - http://cdn.myanimelist.net/images/anime/7/40581.jpg - Onegai My Melody: Yuu & Ai - - - 3 - 12965 - Studio Comet - http://cdn.myanimelist.net/images/anime/12/36617.jpg - Jewelpet Movie: Sweets Dance Princess - - - 2 - 16656 - - http://cdn.myanimelist.net/images/anime/13/44874.jpg - Zettai Junpaku: Mahou Shoujo - - - 2 - 13851 - Xebec - http://cdn.myanimelist.net/images/anime/7/41167.jpg - To LOVE-Ru Darkness OVA - - - 3 - 12049 - TV Tokyo, Satelight, Dentsu, A-1 Pictures, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/8/50103.jpg - Fairy Tail: Houou no Miko - - - 4 - 14647 - J.C. Staff, Artland - http://cdn.myanimelist.net/images/anime/3/40677.jpg - Tantei Opera Milky Holmes: Alternative - - - 2 - 10928 - Diomedea - http://cdn.myanimelist.net/images/anime/10/39227.jpg - Nogizaka Haruka no Himitsu: Finale - - - 4 - 13587 - Aniplex, AIC A.S.T.A., Sentai Filmworks - http://cdn.myanimelist.net/images/anime/5/37787.jpg - Persona 4 The Animation: No One is Alone - - - 2 - 13093 - Hoods Entertainment - http://cdn.myanimelist.net/images/anime/12/41297.jpg - Nazo no Kanojo X: Nazo no Natsu Matsuri - - - 4 - 15325 - Geneon Universal Entertainment, Sotsu Agency, Feel, Lantis - http://cdn.myanimelist.net/images/anime/6/41365.jpg - Dakara Boku wa, H ga Dekinai. Recap - - - 3 - 13935 - - http://cdn.myanimelist.net/images/anime/13/41059.jpg - Houkago Midnighters - - - 4 - 15993 - - http://cdn.myanimelist.net/images/anime/9/43145.jpg - Ginga e Kickoff!!: Natsuyasumi Special - - - 4 - 15323 - Toei Animation, Fuji TV - http://cdn.myanimelist.net/images/anime/5/41415.jpg - One Piece: Episode of Nami - Koukaishi no Namida to Nakama no Kizuna - - - 6 - 19187 - - http://cdn.myanimelist.net/images/anime/10/52123.jpg - Fake Doll - - - 2 - 15431 - Sunrise, Aniplex, Lantis, Mainichi Broadcasting, Natsuiro Kiseki Production Partners - http://cdn.myanimelist.net/images/anime/6/41923.jpg - Natsuiro Kiseki: 15-kaime no Natsuyasumi - - - 6 - 19081 - Sega - http://cdn.myanimelist.net/images/anime/2/51255.jpg - Weekender Girl - - - 2 - 14949 - - http://cdn.myanimelist.net/images/anime/12/53753.jpg - Yurumates (2012) - - - 5 - 15359 - Toei Animation - http://cdn.myanimelist.net/images/anime/12/41893.jpg - Kyousou Giga (2012) - + + + + Summer 2012 + 1380319211 + + + 3 + 10681 + 6288 + Production I.G, Aniplex, Dentsu, FUNimation Entertainment, Shochiku, Kadokawa Shoten, Mainichi Broadcasting, Dwango + http://cdn.myanimelist.net/images/anime/9/34495.jpg + Blood-C: The Last Dark + + + 4 + 14317 + 7111 + Production I.G + http://cdn.myanimelist.net/images/anime/10/39329.jpg + Computer Kakumei: Saikyou x Saisoku no Zunou Tanjou + + + 3 + 14853 + 7177 + Sunrise + http://cdn.myanimelist.net/images/anime/12/53171.jpg + Tsukumo + + + 3 + 13639 + 7003 + Sunrise + http://cdn.myanimelist.net/images/anime/10/53165.jpg + Hi no Youjin + + + 1 + 12031 + 6648 + Studio Pierrot, FUNimation Entertainment, NHK + http://cdn.myanimelist.net/images/anime/13/39511.jpg + Kingdom + + + 6 + 14359 + 7118 + + http://cdn.myanimelist.net/images/anime/11/39405.jpg + Vocaloid China Project Anime PV + + + 2 + 13055 + 6873 + Studio Deen + http://cdn.myanimelist.net/images/anime/13/44916.jpg + Sankarea OVA + + + 3 + 14267 + 7106 + AIC A.S.T.A., Index + http://cdn.myanimelist.net/images/anime/3/50415.jpg + Persona 4 The Animation: The Factor of Hope + + + 2 + 14479 + 7127 + AIC + http://cdn.myanimelist.net/images/anime/6/39537.jpg + Mahou Tsukai Nara Miso wo Kue! + + + 3 + 11001 + 6436 + Production I.G + http://cdn.myanimelist.net/images/anime/9/39273.jpg + Toshokan Sensou: Kakumei no Tsubasa + + + 4 + 11889 + 6627 + Aniplex, A-1 Pictures + http://cdn.myanimelist.net/images/anime/8/33267.jpg + The iDOLM@STER: 765 Pro to Iu Monogatari + + + 1 + 14093 + 7080 + TV Tokyo, Oriental Light and Magic, MediaNet, Half H.P Studio, Studio Jack + http://cdn.myanimelist.net/images/anime/3/39007.jpg + Pokemon Best Wishes! Season 2 + + + 5 + 14537 + 7134 + + http://cdn.myanimelist.net/images/anime/3/39633.jpg + Cinnamon no Parade + + + 4 + 20547 + + Gainax + http://cdn.myanimelist.net/images/anime/5/54555.jpg + Top wo Nerae 2! Diebuster! Science Lesson + + + 2 + 12581 + 6768 + GoHands + http://cdn.myanimelist.net/images/anime/7/40395.jpg + Asa Made Jugyou Chu! + + + 3 + 12113 + 6664 + Studio 4°C, Viz Media, NYAV Post, Yahoo! Japan + http://cdn.myanimelist.net/images/anime/12/37193.jpg + Berserk: Ougon Jidaihen II - Doldrey Kouryaku + + + 2 + 13727 + 7019 + Production I.G, Xebec + http://cdn.myanimelist.net/images/anime/10/38259.jpg + Rinne no Lagrange: Kamogawa Days + + + 3 + 20469 + 8171 + Himeyuri Alumnae Incorporated Foundation, ASIA Documentary Productions + http://cdn.myanimelist.net/images/anime/5/54359.jpg + Himeyuri + + + 3 + 12355 + 6711 + Madhouse Studios, VAP, Dentsu, Yomiuri Telecasting Corporation, FUNimation Entertainment, Kadokawa Shoten, Toho Company, Studio Chizu + http://cdn.myanimelist.net/images/anime/9/35721.jpg + Ookami Kodomo no Ame to Yuki + + + 4 + 13855 + 7048 + Arms, Genco, Media Factory, Sentai Filmworks, Hobby Japan + http://cdn.myanimelist.net/images/anime/7/38419.jpg + Queen's Blade: Rebellion Specials + + + 4 + 15219 + 7232 + + http://cdn.myanimelist.net/images/anime/10/42325.jpg + .hack//Versus: The Thanatos Report + + + 3 + 12507 + 6752 + Xebec, AIC + http://cdn.myanimelist.net/images/anime/6/45927.jpg + Uchuu Senkan Yamato 2199 Movie 2: Taiyou-ken no Shitou + + + 4 + 17791 + 7739 + Kyoto Animation, Lantis, Rakuonsha + http://cdn.myanimelist.net/images/anime/13/47365.jpg + Kyoto Animation: Ikitaku Naru Omise-hen + + + 2 + 14039 + 7070 + G-mode + http://cdn.myanimelist.net/images/anime/7/38787.jpg + Katayoku no Khronos Gear + + + 1 + 13333 + 6941 + Bandai Visual, Dentsu, Sotsu Agency, Lantis, P.A. Works, Pony Canyon, Sentai Filmworks, Showgate + http://cdn.myanimelist.net/images/anime/6/50423.jpg + Tari Tari + + + 4 + 14675 + 7154 + Production I.G, Xebec + http://cdn.myanimelist.net/images/anime/7/39929.jpg + Rinne no Lagrange: Kamogawa Memoria + + + 1 + 12967 + 6859 + J.C. Staff, Frontier Works, Movic, Sentai Filmworks, Warner Bros., Showgate + http://cdn.myanimelist.net/images/anime/9/39495.jpg + Arcana Famiglia + + + 1 + 11021 + 6442 + TV Tokyo, Satelight, Avex Entertainment, AT-X, Magic Capsule, Ixtl + http://cdn.myanimelist.net/images/anime/3/44462.jpg + Muv-Luv Alternative: Total Eclipse + + + 1 + 10357 + 6146 + Sotsu Agency, AIC A.S.T.A., Lantis, Pony Canyon, Movic, Sentai Filmworks, Marvelous AQL + http://cdn.myanimelist.net/images/anime/4/45704.jpg + Jinrui wa Suitai Shimashita + + + 1 + 14277 + 7107 + AT-X, Silver Link, Takeshobo, Dream Creation + http://cdn.myanimelist.net/images/anime/10/50537.jpg + Chitose Get You!! + + + 1 + 13349 + 6943 + TYO Animations + http://cdn.myanimelist.net/images/anime/7/42007.jpg + Chouyaku Hyakuninisshu: Uta Koi. + + + 2 + 14935 + 7183 + + http://cdn.myanimelist.net/images/anime/3/40543.jpg + Utae Meloetta: Rinka no Mi wo Sagase! + + + 1 + 14693 + 7156 + DAX Production, Takeshobo, C2C, Right Gauge, Dwango Music Entertainment + http://cdn.myanimelist.net/images/anime/5/40001.jpg + Yurumates 3D Plus + + + 1 + 12403 + 6724 + Dogakobo, Pony Canyon, DAX Production + http://cdn.myanimelist.net/images/anime/9/39071.jpg + Yuru Yuri♪♪ + + + 1 + 13535 + 6988 + Sunrise, TV Tokyo, Dentsu, FUNimation Entertainment, Kodansha, Trinity Sound, Sony Music Entertainment, Studio Jack + http://cdn.myanimelist.net/images/anime/8/39333.jpg + Binbougami ga! + + + 1 + 12293 + 6702 + Diomedea, Lantis, AT-X, Barnum Studio, Magic Capsule, Sentai Filmworks, Warner Bros., The Klock Worx + http://cdn.myanimelist.net/images/anime/4/37939.jpg + Campione!: Matsurowanu Kamigami to Kamigoroshi no Maou + + + 1 + 12549 + 7656 + Geneon Universal Entertainment, Sotsu Agency, Feel, Lantis, AT-X, Sentai Filmworks, The Klock Worx + http://cdn.myanimelist.net/images/anime/6/38281.jpg + Dakara Boku wa, H ga Dekinai. + + + 1 + 13585 + 6996 + Dogakobo, Fuji TV, Toho Company, Sentai Filmworks, Sony Music Entertainment + http://cdn.myanimelist.net/images/anime/11/38181.jpg + Natsuyuki Rendezvous + + + 1 + 13367 + 6947 + Lantis, TBS, DAX Production, Sentai Filmworks, Studio Gokumi + http://cdn.myanimelist.net/images/anime/11/39587.jpg + Kono Naka ni Hitori, Imouto ga Iru! + + + 1 + 13409 + 6957 + Aniplex, Dentsu, Telecom Animation Film, Kodansha, Fuji TV, Shirogumi + http://cdn.myanimelist.net/images/anime/11/39775.jpg + Moyashimon Returns + + + 1 + 13161 + 6896 + Arms, Genco, FUNimation Entertainment, Lantis, Media Factory, AT-X, Showgate + http://cdn.myanimelist.net/images/anime/2/37577.jpg + Hagure Yuusha no Estetica + + + 1 + 12679 + 6789 + J.C. Staff, Starchild Records, Mainichi Broadcasting, Kodansha + http://cdn.myanimelist.net/images/anime/8/48925.jpg + Joshiraku + + + 1 + 12175 + 6682 + Aniplex, Lantis, Magic Capsule, Sentai Filmworks, AIC Build + http://cdn.myanimelist.net/images/anime/4/42015.jpg + Koi to Senkyo to Chocolate + + + 2 + 15077 + 7208 + CoMix Wave + http://cdn.myanimelist.net/images/anime/11/40907.jpg + Tabisuru Nuigurumi: Traveling Daru + + + 1 + 11783 + 6598 + Aniplex, Seven Arcs + http://cdn.myanimelist.net/images/anime/9/42105.jpg + Dog Days' + + + 3 + 14381 + 7122 + + http://cdn.myanimelist.net/images/anime/3/39427.jpg + Sore Ike! Anpanman: Yomigaere Bananajima + + + 4 + 15927 + 7342 + Lerche + http://cdn.myanimelist.net/images/anime/4/43321.jpg + Carnival Phantasm: HibiChika Special + + + 3 + 13215 + 6912 + Tezuka Productions + http://cdn.myanimelist.net/images/anime/13/36995.jpg + Guskou Budori no Denki (2012) + + + 1 + 15069 + 7207 + + http://cdn.myanimelist.net/images/anime/11/40909.jpg + Robin-kun to 100 nin no Otomodachi Season 2 + + + 1 + 14333 + 7112 + TV Tokyo, Toei Animation, Dentsu + http://cdn.myanimelist.net/images/anime/3/39357.jpg + Tanken Driland + + + 1 + 11887 + 6626 + Starchild Records, Silver Link, Sentai Filmworks, Enterbrain + http://cdn.myanimelist.net/images/anime/2/39665.jpg + Kokoro Connect + + + 2 + 13469 + 6974 + Kyoto Animation + http://cdn.myanimelist.net/images/anime/6/50363.jpg + Hyouka: Motsubeki Mono wa + + + 1 + 12487 + 6746 + Sunrise, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/6/38227.jpg + Kyoukaisenjou no Horizon II + + + 1 + 11757 + 6589 + Aniplex, A-1 Pictures, Genco, DAX Production, Aniplex of America, ASCII Media Works + http://cdn.myanimelist.net/images/anime/11/39717.jpg + Sword Art Online + + + 1 + 12281 + 6699 + Production I.G, Xebec, Viz Media + http://cdn.myanimelist.net/images/anime/4/39321.jpg + Rinne no Lagrange Season 2 + + + 1 + 13115 + 6884 + Studio Deen, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/12/39163.jpg + Hakuouki Reimeiroku + + + 1 + 11933 + 7657 + Madhouse Studios, Pony Canyon, AT-X, Studio Gokumi, The Klock Worx, SoftBank Creative Corp. + http://cdn.myanimelist.net/images/anime/11/39249.jpg + Oda Nobuna no Yabou + + + 4 + 12673 + 6787 + Feel + http://cdn.myanimelist.net/images/anime/3/35569.jpg + Papa no Iukoto wo Kikinasai!: Pokkapoka + + + 3 + 12671 + 6786 + TV Tokyo, Oriental Light and Magic, Shogakukan Productions, Toho Company, Studio Jack + http://cdn.myanimelist.net/images/anime/11/38311.jpg + Pokemon Best Wishes! Season 2: Kyurem vs. Seikenshi + + + 3 + 10153 + 6056 + Seven Arcs + http://cdn.myanimelist.net/images/anime/13/35733.jpg + Mahou Shoujo Lyrical Nanoha: The Movie 2nd A's + + + 4 + 13799 + 7032 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/3/49309.jpg + Pokemon: Meloetta no Kirakira Recital + + + 5 + 14073 + 7077 + AIC, Sentai Filmworks, AMG MUSIC + http://cdn.myanimelist.net/images/anime/7/38889.jpg + Ebiten: Kouritsu Ebisugawa Koukou Tenmonbu + + + 6 + 17293 + 7629 + Studio 4°C, Shirogumi + http://cdn.myanimelist.net/images/anime/4/46189.jpg + Love Like Aliens + + + 3 + 8888 + 5518 + Sunrise, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/5/39295.jpg + Code Geass: Boukoku no Akito 1 - Yokuryuu wa Maiorita + + + 2 + 14627 + 7143 + Takeshobo, Seven + http://cdn.myanimelist.net/images/anime/10/40081.jpg + Recorder to Randoseru + + + 6 + 20529 + + Sony Music Entertainment + http://cdn.myanimelist.net/images/anime/9/54519.jpg + Harinezumi + + + 3 + 14629 + 7144 + + http://cdn.myanimelist.net/images/anime/7/39815.jpg + Starship Troopers: Invasion + + + 3 + 17573 + 7999 + A-1 Pictures, Digital Media Lab + http://cdn.myanimelist.net/images/anime/2/47201.jpg + Planetarium Uchuu Kyoudai: Itten no Hikari + + + 4 + 13859 + 7050 + J.C. Staff, Viz Media + http://cdn.myanimelist.net/images/anime/10/38469.jpg + Accel World Specials + + + 4 + 16532 + 7453 + + http://cdn.myanimelist.net/images/anime/6/44620.jpg + Beelzebub: Kaiketsu!! Beel-bo Meitantei Suiri + + + 4 + 13411 + 6958 + Production I.G, Aniplex + http://cdn.myanimelist.net/images/anime/12/37525.jpg + Guilty Crown: Lost Christmas + + + 2 + 14631 + 7145 + J.C. Staff + http://cdn.myanimelist.net/images/anime/2/39817.jpg + Dangerous Jiisan Ja + + + 4 + 15487 + 7262 + Production I.G + http://cdn.myanimelist.net/images/anime/12/42751.jpg + Kuroko no Basket NG-shuu + + + 2 + 12685 + 6791 + Sunrise + http://cdn.myanimelist.net/images/anime/7/36175.jpg + Code Geass: Nunnally in Wonderland + + + 4 + 15505 + 7264 + Production I.G, Avex Entertainment, Sega, DAX Production, Volks + http://cdn.myanimelist.net/images/anime/13/42153.jpg + Shining Hearts: Shiawase no Pan Specials + + + 4 + 15769 + 7304 + Production I.G, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/12/42789.jpg + Shining Hearts: Shiawase no Pan - Kokoro ga Todoita Picture Drama + + + 3 + 13667 + 7008 + Studio Pierrot, TV Tokyo, Aniplex, Dentsu, Toho Company + http://cdn.myanimelist.net/images/anime/6/51863.jpg + Naruto: Shippuuden Movie 6 - Road to Ninja + + + 2 + 9988 + 5980 + Silver Link + http://cdn.myanimelist.net/images/anime/12/37657.jpg + Otome wa Boku ni Koishiteru: Futari no Elder + + + 1 + 14563 + 7137 + AIC Frontier + http://cdn.myanimelist.net/images/anime/4/39693.jpg + Maji de Otaku na English! Ribbon-chan: Eigo de Tatakau Mahou Shoujo + + + 2 + 13807 + 7033 + Asread + http://cdn.myanimelist.net/images/anime/5/38331.jpg + Corpse Party: Missing Footage + + + 2 + 15211 + 7231 + SynergySP + http://cdn.myanimelist.net/images/anime/10/42143.jpg + Nazotoki-hime wa Meitantei♥ + + + 5 + 15005 + 7196 + Lantis, Half H.P Studio, Silver Link + http://cdn.myanimelist.net/images/anime/6/46553.jpg + Kyou no Asuka Show + + + 2 + 13283 + 6932 + Dentsu, TYO Animations + http://cdn.myanimelist.net/images/anime/12/39661.jpg + One Off + + + 2 + 13267 + 6929 + Diomedea + http://cdn.myanimelist.net/images/anime/4/39055.jpg + Shinryaku!! Ika Musume + + + 2 + 12879 + 6834 + Gainax, TV Tokyo, Kadokawa Shoten, Kadokawa Pictures Japan, The Klock Worx + http://cdn.myanimelist.net/images/anime/13/46959.jpg + Dantalian no Shoka: Ibarahime + + + 3 + 14947 + 7186 + + http://cdn.myanimelist.net/images/anime/7/40581.jpg + Onegai My Melody: Yuu & Ai + + + 3 + 12965 + 6858 + Studio Comet + http://cdn.myanimelist.net/images/anime/12/36617.jpg + Jewelpet Movie: Sweets Dance Princess + + + 2 + 16656 + 7484 + + http://cdn.myanimelist.net/images/anime/13/44874.jpg + Zettai Junpaku: Mahou Shoujo + + + 2 + 13851 + 7047 + Xebec + http://cdn.myanimelist.net/images/anime/7/41167.jpg + To LOVE-Ru Darkness OVA + + + 3 + 12049 + 6652 + TV Tokyo, Satelight, Dentsu, A-1 Pictures, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/8/50103.jpg + Fairy Tail: Houou no Miko + + + 4 + 14647 + 7147 + J.C. Staff, Artland + http://cdn.myanimelist.net/images/anime/3/40677.jpg + Tantei Opera Milky Holmes: Alternative + + + 2 + 10928 + 6410 + Diomedea + http://cdn.myanimelist.net/images/anime/10/39227.jpg + Nogizaka Haruka no Himitsu: Finale + + + 4 + 13587 + 6997 + Aniplex, AIC A.S.T.A., Sentai Filmworks + http://cdn.myanimelist.net/images/anime/5/37787.jpg + Persona 4 The Animation: No One is Alone + + + 2 + 13093 + 6882 + Hoods Entertainment + http://cdn.myanimelist.net/images/anime/12/41297.jpg + Nazo no Kanojo X: Nazo no Natsu Matsuri + + + 4 + 15325 + 6761 + Geneon Universal Entertainment, Sotsu Agency, Feel, Lantis + http://cdn.myanimelist.net/images/anime/6/41365.jpg + Dakara Boku wa, H ga Dekinai. Recap + + + 3 + 13935 + 7055 + + http://cdn.myanimelist.net/images/anime/13/41059.jpg + Houkago Midnighters + + + 4 + 15993 + 7354 + + http://cdn.myanimelist.net/images/anime/9/43145.jpg + Ginga e Kickoff!!: Natsuyasumi Special + + + 4 + 15323 + 7240 + Toei Animation, Fuji TV + http://cdn.myanimelist.net/images/anime/5/41415.jpg + One Piece: Episode of Nami - Koukaishi no Namida to Nakama no Kizuna + + + 6 + 19187 + + + http://cdn.myanimelist.net/images/anime/10/52123.jpg + Fake Doll + + + 2 + 15431 + 7255 + Sunrise, Aniplex, Lantis, Mainichi Broadcasting, Natsuiro Kiseki Production Partners + http://cdn.myanimelist.net/images/anime/6/41923.jpg + Natsuiro Kiseki: 15-kaime no Natsuyasumi + + + 6 + 19081 + + Sega + http://cdn.myanimelist.net/images/anime/2/51255.jpg + Weekender Girl + + + 2 + 14949 + 7187 + + http://cdn.myanimelist.net/images/anime/12/53753.jpg + Yurumates (2012) + + + 5 + 15359 + 7243 + Toei Animation + http://cdn.myanimelist.net/images/anime/12/41893.jpg + Kyousou Giga (2012) + \ No newline at end of file diff --git a/data/db/season/2012_winter.xml b/data/db/season/2012_winter.xml index acf743aa2..18683d519 100644 --- a/data/db/season/2012_winter.xml +++ b/data/db/season/2012_winter.xml @@ -1,742 +1,847 @@ - - - - Winter 2012 - 1380299999 - - - 5 - 10893 - Toei Animation - http://cdn.myanimelist.net/images/anime/3/30075.jpg - Kyousou Giga - - - 3 - 12001 - Toei Animation - http://cdn.myanimelist.net/images/anime/4/33839.jpg - One Piece 3D: Gekisou! Trap Coaster - - - 4 - 11777 - Telecom Animation Film - http://cdn.myanimelist.net/images/anime/12/32929.jpg - Lupin III: Chi no Kokuin - Eien no Mermaid - - - 4 - 17209 - Digital Media Lab - http://cdn.myanimelist.net/images/anime/8/45961.jpg - Suzy's Zoo: Daisuki! Witzy - Happy Birthday - - - 3 - 9617 - Kyoto Animation, Pony Canyon, Sentai Filmworks, Animation Do - http://cdn.myanimelist.net/images/anime/6/41163.jpg - K-On! Movie - - - 2 - 10934 - Zexcs - http://cdn.myanimelist.net/images/anime/13/30176.jpg - Itsuka Tenma no Kuro Usagi OVA - - - 2 - 10794 - Sentai Filmworks, 8bit, Sony Music Communications, Project IS - http://cdn.myanimelist.net/images/anime/4/29805.jpg - IS: Infinite Stratos Encore - Koi ni Kogareru Sextet - - - 6 - 12623 - U/M/A/A Inc. - http://cdn.myanimelist.net/images/anime/10/35355.jpg - Egomama - - - 4 - 18549 - Production I.G, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/4/49863.jpg - Sengoku Basara Movie: 4-Koma Gekijou - Another Last Party - - - 5 - 12433 - Gonzo - http://cdn.myanimelist.net/images/anime/8/34929.jpg - Kyokugen Dasshutsu Adv: Zennin Shibou Desu Prologue - - - 2 - 10933 - AIC - http://cdn.myanimelist.net/images/anime/5/30175.jpg - R-15 OVA - - - 4 - 13675 - CoMix Wave - http://cdn.myanimelist.net/images/anime/11/47427.jpg - Taisei Kensetsu: Bosporus Kaikyou Tunnel - - - 2 - 11917 - SynergySP - http://cdn.myanimelist.net/images/anime/7/38269.jpg - Major: World Series - - - 3 - 10115 - Sony Music Entertainment, Yahoo! Japan - http://cdn.myanimelist.net/images/anime/2/35815.jpg - Friends: Mononoke Shima no Naki - - - 4 - 12429 - Gonzo, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/7/34925.jpg - Last Exile: Ginyoku no Fam Recaps - - - 4 - 15533 - AIC - http://cdn.myanimelist.net/images/anime/11/42233.jpg - Yakimochi Caprice - - - 2 - 11441 - Studio Deen, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/2/39663.jpg - Rurouni Kenshin: Meiji Kenkaku Romantan - Shin Kyoto Hen - - - 4 - 12231 - Toei Animation - http://cdn.myanimelist.net/images/anime/8/35005.jpg - Dragon Ball: Episode of Bardock - - - 5 - 13041 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/9/36619.jpg - Battle Break - - - 4 - 13067 - Studio Pierrot - http://cdn.myanimelist.net/images/anime/12/36679.jpg - Beelzebub: Sakigake!! Beel to Shinsengumi - - - 4 - 11553 - J.C. Staff - http://cdn.myanimelist.net/images/anime/2/50371.jpg - Toradora!: Bentou no Gokui - - - 4 - 10604 - J.C. Staff, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/2/29138.jpg - Hidan no Aria Special - - - 2 - 11161 - Dogakobo - http://cdn.myanimelist.net/images/anime/10/30839.jpg - Hoshizora e Kakaru Hashi OVA - - - 4 - 12447 - AT-X, David Production - http://cdn.myanimelist.net/images/anime/13/34953.jpg - Ben-To Picture Drama - - - 4 - 12449 - AIC, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/6/34957.jpg - Maken-Ki! Specials - - - 5 - 13515 - - http://cdn.myanimelist.net/images/anime/11/37861.jpg - Neo Satomi Hakkenden: Satomi-chanchi no Hachi Danshi - - - 5 - 12157 - Asread, Enterbrain, StudioRF Inc. - http://cdn.myanimelist.net/images/anime/10/48839.jpg - Busou Chuugakusei: Basket Army - - - 4 - 13829 - - http://cdn.myanimelist.net/images/anime/6/38507.jpg - Kamiusagi Rope: Christmas - - - 3 - 10999 - Oriental Light and Magic, Studio Jack - http://cdn.myanimelist.net/images/anime/7/44972.jpg - Inazuma Eleven Go: Kyuukyoku no Kizuna Gryphon - - - 5 - 12755 - - http://cdn.myanimelist.net/images/anime/9/35793.jpg - Mahou Tsukai Jiji - - - 4 - 13517 - A-1 Pictures - http://cdn.myanimelist.net/images/anime/11/47725.jpg - Ryuugajou Nanana no Maizoukin - - - 4 - 12585 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/3/35261.jpg - Inazuma Eleven Go Recap - - - 2 - 12451 - AIC - http://cdn.myanimelist.net/images/anime/4/34983.jpg - Atlanger - - - 2 - 12565 - Lerche - http://cdn.myanimelist.net/images/anime/3/51045.jpg - Fate/Prototype - - - 4 - 12699 - Sunrise - http://cdn.myanimelist.net/images/anime/5/48841.jpg - Tales of Gekijou - - - 4 - 12419 - Production I.G - http://cdn.myanimelist.net/images/anime/3/34931.jpg - Guilty Crown Kiseki: Reassortment - - - 1 - 11665 - Aniplex, Brains Base, NAS, NIS America, Inc., Sony Music Entertainment - http://cdn.myanimelist.net/images/anime/3/37449.jpg - Natsume Yuujinchou Shi - - - 1 - 11341 - J.C. Staff, Artland, FUNimation Entertainment, Lantis, Dwango, Bushiroad Inc. - http://cdn.myanimelist.net/images/anime/2/35372.jpg - Tantei Opera Milky Holmes Dai 2 Maku - - - 1 - 11371 - Production I.G, TV Tokyo, Bandai Visual, NAS, M.S.C, TV Tokyo Music, Marvelous AQL - http://cdn.myanimelist.net/images/anime/10/33591.jpg - New Prince of Tennis - - - 1 - 11079 - J.C. Staff, Pony Canyon, TBS, Magic Capsule, Sentai Filmworks, Pony Canyon Enterprises - http://cdn.myanimelist.net/images/anime/10/32723.jpg - Kill Me Baby - - - 1 - 11617 - Genco, FUNimation Entertainment, Lantis, TNK, AT-X, PRA, Showgate - http://cdn.myanimelist.net/images/anime/2/32527.jpg - High School DxD - - - 1 - 11235 - AIC, TBS, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/13/33359.jpg - Amagami SS+ Plus - - - 1 - 11491 - Takeshobo, Seven, Dream Creation - http://cdn.myanimelist.net/images/anime/13/33003.jpg - Recorder to Randoseru Do♪ - - - 1 - 11319 - J.C. Staff, Sentai Filmworks, Cospa, Showgate - http://cdn.myanimelist.net/images/anime/4/34407.jpg - Zero no Tsukaima F - - - 3 - 11635 - Sunrise - http://cdn.myanimelist.net/images/anime/12/34505.jpg - Sacred Seven: Shirogane no Tsubasa - - - 1 - 11697 - TV Asahi, Shin-Ei Animation - http://cdn.myanimelist.net/images/anime/12/35467.jpg - Area no Kishi - - - 3 - 10690 - TV Tokyo, Dentsu, TV Osaka, Media Factory, Asia-Do, GAGA Communications, Yahoo! Japan - http://cdn.myanimelist.net/images/anime/5/34487.jpg - Magic Tree House - - - 1 - 11751 - Satelight, Encourage Films, Memory-Tech, Dwango, Bushiroad Inc. - http://cdn.myanimelist.net/images/anime/12/42943.jpg - Senki Zesshou Symphogear: Meteoroid-Falling, Burning, and Disappear, Then... - - - 1 - 11597 - Aniplex, Shaft, Kodansha, Rakuonsha, Aniplex of America - http://cdn.myanimelist.net/images/anime/10/35619.jpg - Nisemonogatari - - - 1 - 8917 - Satelight, Starchild Records, Magic Capsule, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/13/36247.jpg - Mouretsu Pirates - - - 1 - 11227 - Production I.G, Bandai Visual, Xebec, Yomiuri Telecasting Corporation, Viz Media, flying DOG, Dwango - http://cdn.myanimelist.net/images/anime/7/50439.jpg - Rinne no Lagrange - - - 1 - 11241 - TMS Entertainment, Media Factory, NIS America, Inc., Studio Saki Makura - http://cdn.myanimelist.net/images/anime/13/34043.jpg - Brave 10 - - - 1 - 12021 - Studio Deen, DAX Production, Dream Creation, Möbius Tone - http://cdn.myanimelist.net/images/anime/11/35455.jpg - Poyopoyo Kansatsu Nikki - - - 1 - 10447 - Satelight, FUNimation Entertainment, Media Factory, Bandai, 8bit - http://cdn.myanimelist.net/images/anime/9/42805.jpg - Aquarion Evol - - - 1 - 11843 - Sunrise, TV Tokyo, Square Enix, NIS America, Inc., Trinity Sound - http://cdn.myanimelist.net/images/anime/3/33257.jpg - Danshi Koukousei no Nichijou - - - 1 - 11111 - Lantis, Kadokawa Shoten, P.A. Works, Toho Company, Sentai Filmworks, The Klock Worx, Bandai Namco Live Creative, NTT Docomo - http://cdn.myanimelist.net/images/anime/6/41865.jpg - Another - - - 1 - 11433 - J.C. Staff, Geneon Universal Entertainment, Genco, AT-X, Sentai Filmworks, Bushiroad Inc., Showgate - http://cdn.myanimelist.net/images/anime/10/34453.jpg - Ano Natsu de Matteru - - - 6 - 15653 - - http://cdn.myanimelist.net/images/anime/10/42521.jpg - Stay The Same - - - 6 - 15453 - Diomedea - http://cdn.myanimelist.net/images/anime/8/41945.jpg - Shinryaku! Ika Musume: Kore ga Umi e no Ai Jana-ika! - - - 1 - 11179 - Feel, Starchild Records, Sentai Filmworks, The Klock Worx, PPP, Studio Mausu - http://cdn.myanimelist.net/images/anime/3/41269.jpg - Papa no Iukoto wo Kikinasai! - - - 3 - 18491 - - http://cdn.myanimelist.net/images/anime/8/49607.jpg - Xi Yang Yang Yu Hui Tai Lang: Zhi Kaixin Chuang Long Nian - - - 6 - 12683 - U/M/A/A Inc. - http://cdn.myanimelist.net/images/anime/12/35615.jpg - Yumeyume - - - 1 - 11013 - Aniplex, Square Enix, Mainichi Broadcasting, Movic, David Production, Sentai Filmworks, Inu x Boku SS Production Partners - http://cdn.myanimelist.net/images/anime/12/35893.jpg - Inu x Boku SS - - - 1 - 12321 - Gonzo, Dentsu, Fuji TV, Toho Company, DLE, Discotek, Sony Music Entertainment - http://cdn.myanimelist.net/images/anime/5/35635.jpg - Thermae Romae - - - 5 - 12865 - - http://cdn.myanimelist.net/images/anime/5/36083.jpg - Ai wa Kat-tun - - - 1 - 12651 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/13/35735.jpg - Danball Senki W - - - 2 - 5784 - AIC, Media Blasters, KENMedia - http://cdn.myanimelist.net/images/anime/9/34911.jpg - Ai no Kusabi (2012) - - - 3 - 11375 - Bandai, CyberConnect2, flying DOG, Asmik Ace Entertainment - http://cdn.myanimelist.net/images/anime/2/35633.jpg - .hack//The Movie: Sekai no Mukou ni - - - 2 - 12643 - - http://cdn.myanimelist.net/images/anime/13/35451.jpg - Happening Star - - - 1 - 13207 - - http://cdn.myanimelist.net/images/anime/2/37123.jpg - Himitsukessha Taka no Tsume Gaiden: Mukashi no Yoshida-kun - - - 1 - 11769 - LMD, Cammot - http://cdn.myanimelist.net/images/anime/11/35659.jpg - Gokujo. - - - 4 - 14049 - Manglobe - http://cdn.myanimelist.net/images/anime/3/38799.jpg - Mashiroiro Symphony: Airi ga Anata no Kanojo ni!? - - - 4 - 13561 - Production I.G, Aniplex, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/4/37761.jpg - Guilty Crown Specials - - - 2 - 13673 - Studio Izena - http://cdn.myanimelist.net/images/anime/9/37951.jpg - Otome Nadeshiko Koi Techou - - - 4 - 12823 - J.C. Staff, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/11/36063.jpg - Shakugan no Shana III (Final) Specials - - - 4 - 12921 - DAX Production, Takeshobo, Seven - http://cdn.myanimelist.net/images/anime/3/36275.jpg - Morita-san wa Mukuchi. Specials - - - 5 - 13255 - Studio 4°C - http://cdn.myanimelist.net/images/anime/5/37107.jpg - Haru wa Kuru - - - 4 - 12503 - FUNimation Entertainment, Asread - http://cdn.myanimelist.net/images/anime/2/38423.jpg - Mirai Nikki (TV): Ura Mirai Nikki - - - 4 - 13619 - Shaft - http://cdn.myanimelist.net/images/anime/8/37831.jpg - Sayonara Zetsubou Sensei Special Omake - - - 4 - 10469 - Shaft - http://cdn.myanimelist.net/images/anime/6/28743.jpg - Sayonara Zetsubou Sensei Special - - - 5 - 13543 - - http://cdn.myanimelist.net/images/anime/5/37869.jpg - Nantokashite Alguard - - - 5 - 13541 - - http://cdn.myanimelist.net/images/anime/10/37867.jpg - Rui no Masaiban - - - 5 - 12907 - - http://cdn.myanimelist.net/images/anime/4/36235.jpg - Sekiei Ayakashi Mangatan - - - 1 - 11285 - Aniplex, Dentsu, FUNimation Entertainment, Ordet, SANZIGEN, Ultra Super Pictures, Sony Music Entertainment, Fuji Pacific Music Publishing - http://cdn.myanimelist.net/images/anime/5/53909.jpg - Black★Rock Shooter (TV) - - - 2 - 12599 - - http://cdn.myanimelist.net/images/anime/8/35317.jpg - Ijime - - - 2 - 19849 - CoMix Wave - http://cdn.myanimelist.net/images/anime/4/52931.jpg - Peeping Life 5.0ch - - - 3 - 10218 - Studio 4°C, Viz Media, NYAV Post, Yahoo! Japan - http://cdn.myanimelist.net/images/anime/5/33563.jpg - Berserk: Ougon Jidaihen I - Haou no Tamago - - - 1 - 13167 - - http://cdn.myanimelist.net/images/anime/4/36875.jpg - Zoobles! - - - 1 - 12191 - Toei Animation - http://cdn.myanimelist.net/images/anime/4/36565.jpg - Smile Precure! - - - 4 - 10638 - Shaft, NIS America, Inc. - http://cdn.myanimelist.net/images/anime/3/36709.jpg - Denpa Onna to Seishun Otoko Special - - - 5 - 14235 - - http://cdn.myanimelist.net/images/anime/12/39339.jpg - Salad Land - - - 5 - 15657 - - http://cdn.myanimelist.net/images/anime/7/45694.jpg - Yuke! Shouei-kun - - - 2 - 11339 - Aniplex, Dentsu, Mainichi Broadcasting, Studio Gokumi - http://cdn.myanimelist.net/images/anime/12/35321.jpg - A-Channel: A-Channel+smile - - - 3 - 11705 - FUNimation Entertainment, Oxybot - http://cdn.myanimelist.net/images/anime/2/32715.jpg - Dragon Age: Blood Mage no Seisen - - - 4 - 13831 - - http://cdn.myanimelist.net/images/anime/9/38511.jpg - Kamiusagi Rope: Valentine Day-hen - - - 2 - 9523 - Aniplex, ufotable - http://cdn.myanimelist.net/images/anime/6/31903.jpg - Minori Scramble! - - - 6 - 12637 - - http://cdn.myanimelist.net/images/anime/6/40821.jpg - Mo Gyutto Love de Sekkinchuu! - - - 2 - 12437 - Aniplex, ufotable - http://cdn.myanimelist.net/images/anime/2/34939.jpg - Yuri Seijin Naoko-san (2012) - - - 2 - 10417 - Aniplex, ufotable, Aniplex of America - http://cdn.myanimelist.net/images/anime/9/31905.jpg - Gyo - - - 4 - 10863 - Frontier Works, FUNimation Entertainment, White Fox - http://cdn.myanimelist.net/images/anime/7/36531.jpg - Steins;Gate: Oukoubakko no Poriomania - - - 4 - 13757 - Manglobe - http://cdn.myanimelist.net/images/anime/9/38265.jpg - Mashiroiro Symphony: The Color of Lovers Picture Drama - - - 2 - 11773 - Chaos Project - http://cdn.myanimelist.net/images/anime/9/36361.jpg - To Heart 2: Dungeon Travelers - - - 4 - 13137 - Zexcs - http://cdn.myanimelist.net/images/anime/9/36791.jpg - Itsuka Tenma no Kuro Usagi Special - - - 6 - 13281 - - http://cdn.myanimelist.net/images/anime/5/37387.jpg - FlashBack - - - 4 - 13429 - Studio Rikka, Asmik Ace Entertainment, Purple Cow Studio Japan - http://cdn.myanimelist.net/images/anime/3/37569.jpg - Sakasama no Patema: Beginning of the Day - + + + + Winter 2012 + 1380299999 + + + 5 + 10893 + 6396 + Toei Animation + http://cdn.myanimelist.net/images/anime/3/30075.jpg + Kyousou Giga + + + 3 + 12001 + 6641 + Toei Animation + http://cdn.myanimelist.net/images/anime/4/33839.jpg + One Piece 3D: Gekisou! Trap Coaster + + + 4 + 11777 + 6597 + Telecom Animation Film + http://cdn.myanimelist.net/images/anime/12/32929.jpg + Lupin III: Chi no Kokuin - Eien no Mermaid + + + 4 + 17209 + 7605 + Digital Media Lab + http://cdn.myanimelist.net/images/anime/8/45961.jpg + Suzy's Zoo: Daisuki! Witzy - Happy Birthday + + + 3 + 9617 + 5810 + Kyoto Animation, Pony Canyon, Sentai Filmworks, Animation Do + http://cdn.myanimelist.net/images/anime/6/41163.jpg + K-On! Movie + + + 2 + 10934 + 6412 + Zexcs + http://cdn.myanimelist.net/images/anime/13/30176.jpg + Itsuka Tenma no Kuro Usagi OVA + + + 2 + 10794 + 6350 + Sentai Filmworks, 8bit, Sony Music Communications, Project IS + http://cdn.myanimelist.net/images/anime/4/29805.jpg + IS: Infinite Stratos Encore - Koi ni Kogareru Sextet + + + 6 + 12623 + 6776 + U/M/A/A Inc. + http://cdn.myanimelist.net/images/anime/10/35355.jpg + Egomama + + + 4 + 18549 + + Production I.G, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/4/49863.jpg + Sengoku Basara Movie: 4-Koma Gekijou - Another Last Party + + + 5 + 12433 + 6730 + Gonzo + http://cdn.myanimelist.net/images/anime/8/34929.jpg + Kyokugen Dasshutsu Adv: Zennin Shibou Desu Prologue + + + 2 + 10933 + 6411 + AIC + http://cdn.myanimelist.net/images/anime/5/30175.jpg + R-15 OVA + + + 4 + 13675 + 7011 + CoMix Wave + http://cdn.myanimelist.net/images/anime/11/47427.jpg + Taisei Kensetsu: Bosporus Kaikyou Tunnel + + + 2 + 11917 + 6631 + SynergySP + http://cdn.myanimelist.net/images/anime/7/38269.jpg + Major: World Series + + + 3 + 10115 + 6043 + Sony Music Entertainment, Yahoo! Japan + http://cdn.myanimelist.net/images/anime/2/35815.jpg + Friends: Mononoke Shima no Naki + + + 4 + 12429 + 6728 + Gonzo, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/7/34925.jpg + Last Exile: Ginyoku no Fam Recaps + + + 4 + 15533 + 7268 + AIC + http://cdn.myanimelist.net/images/anime/11/42233.jpg + Yakimochi Caprice + + + 2 + 11441 + 6509 + Studio Deen, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/2/39663.jpg + Rurouni Kenshin: Meiji Kenkaku Romantan - Shin Kyoto Hen + + + 4 + 12231 + 6691 + Toei Animation + http://cdn.myanimelist.net/images/anime/8/35005.jpg + Dragon Ball: Episode of Bardock + + + 5 + 13041 + 6869 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/9/36619.jpg + Battle Break + + + 4 + 13067 + 6875 + Studio Pierrot + http://cdn.myanimelist.net/images/anime/12/36679.jpg + Beelzebub: Sakigake!! Beel to Shinsengumi + + + 4 + 11553 + 6536 + J.C. Staff + http://cdn.myanimelist.net/images/anime/2/50371.jpg + Toradora!: Bentou no Gokui + + + 4 + 10604 + 6263 + J.C. Staff, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/2/29138.jpg + Hidan no Aria Special + + + 2 + 11161 + 6467 + Dogakobo + http://cdn.myanimelist.net/images/anime/10/30839.jpg + Hoshizora e Kakaru Hashi OVA + + + 4 + 12447 + 6735 + AT-X, David Production + http://cdn.myanimelist.net/images/anime/13/34953.jpg + Ben-To Picture Drama + + + 4 + 12449 + 6736 + AIC, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/6/34957.jpg + Maken-Ki! Specials + + + 5 + 13515 + 6985 + + http://cdn.myanimelist.net/images/anime/11/37861.jpg + Neo Satomi Hakkenden: Satomi-chanchi no Hachi Danshi + + + 5 + 12157 + 6679 + Asread, Enterbrain, StudioRF Inc. + http://cdn.myanimelist.net/images/anime/10/48839.jpg + Busou Chuugakusei: Basket Army + + + 4 + 13829 + 7040 + + http://cdn.myanimelist.net/images/anime/6/38507.jpg + Kamiusagi Rope: Christmas + + + 3 + 10999 + 6435 + Oriental Light and Magic, Studio Jack + http://cdn.myanimelist.net/images/anime/7/44972.jpg + Inazuma Eleven Go: Kyuukyoku no Kizuna Gryphon + + + 5 + 12755 + 6804 + + http://cdn.myanimelist.net/images/anime/9/35793.jpg + Mahou Tsukai Jiji + + + 4 + 13517 + 6986 + A-1 Pictures + http://cdn.myanimelist.net/images/anime/11/47725.jpg + Ryuugajou Nanana no Maizoukin + + + 4 + 12585 + 6769 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/3/35261.jpg + Inazuma Eleven Go Recap + + + 2 + 12451 + 6737 + AIC + http://cdn.myanimelist.net/images/anime/4/34983.jpg + Atlanger + + + 2 + 12565 + 6765 + Lerche + http://cdn.myanimelist.net/images/anime/3/51045.jpg + Fate/Prototype + + + 4 + 12699 + 6794 + Sunrise + http://cdn.myanimelist.net/images/anime/5/48841.jpg + Tales of Gekijou + + + 4 + 12419 + 6727 + Production I.G + http://cdn.myanimelist.net/images/anime/3/34931.jpg + Guilty Crown Kiseki: Reassortment + + + 1 + 11665 + 6557 + Aniplex, Brains Base, NAS, NIS America, Inc., Sony Music Entertainment + http://cdn.myanimelist.net/images/anime/3/37449.jpg + Natsume Yuujinchou Shi + + + 1 + 11341 + 6496 + J.C. Staff, Artland, FUNimation Entertainment, Lantis, Dwango, Bushiroad Inc. + http://cdn.myanimelist.net/images/anime/2/35372.jpg + Tantei Opera Milky Holmes Dai 2 Maku + + + 1 + 11371 + 6501 + Production I.G, TV Tokyo, Bandai Visual, NAS, M.S.C, TV Tokyo Music, Marvelous AQL + http://cdn.myanimelist.net/images/anime/10/33591.jpg + New Prince of Tennis + + + 1 + 11079 + 6454 + J.C. Staff, Pony Canyon, TBS, Magic Capsule, Sentai Filmworks, Pony Canyon Enterprises + http://cdn.myanimelist.net/images/anime/10/32723.jpg + Kill Me Baby + + + 1 + 11617 + 6550 + Genco, FUNimation Entertainment, Lantis, TNK, AT-X, PRA, Showgate + http://cdn.myanimelist.net/images/anime/2/32527.jpg + High School DxD + + + 1 + 11235 + 6478 + AIC, TBS, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/13/33359.jpg + Amagami SS+ Plus + + + 1 + 11491 + 6519 + Takeshobo, Seven, Dream Creation + http://cdn.myanimelist.net/images/anime/13/33003.jpg + Recorder to Randoseru Do♪ + + + 1 + 11319 + 6493 + J.C. Staff, Sentai Filmworks, Cospa, Showgate + http://cdn.myanimelist.net/images/anime/4/34407.jpg + Zero no Tsukaima F + + + 3 + 11635 + 6553 + Sunrise + http://cdn.myanimelist.net/images/anime/12/34505.jpg + Sacred Seven: Shirogane no Tsubasa + + + 1 + 11697 + 6567 + TV Asahi, Shin-Ei Animation + http://cdn.myanimelist.net/images/anime/12/35467.jpg + Area no Kishi + + + 3 + 10690 + 6296 + TV Tokyo, Dentsu, TV Osaka, Media Factory, Asia-Do, GAGA Communications, Yahoo! Japan + http://cdn.myanimelist.net/images/anime/5/34487.jpg + Magic Tree House + + + 1 + 11751 + 6587 + Satelight, Encourage Films, Memory-Tech, Dwango, Bushiroad Inc. + http://cdn.myanimelist.net/images/anime/12/42943.jpg + Senki Zesshou Symphogear: Meteoroid-Falling, Burning, and Disappear, Then... + + + 1 + 11597 + 6546 + Aniplex, Shaft, Kodansha, Rakuonsha, Aniplex of America + http://cdn.myanimelist.net/images/anime/10/35619.jpg + Nisemonogatari + + + 1 + 8917 + 5531 + Satelight, Starchild Records, Magic Capsule, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/13/36247.jpg + Mouretsu Pirates + + + 1 + 11227 + 6477 + Production I.G, Bandai Visual, Xebec, Yomiuri Telecasting Corporation, Viz Media, flying DOG, Dwango + http://cdn.myanimelist.net/images/anime/7/50439.jpg + Rinne no Lagrange + + + 1 + 11241 + 6481 + TMS Entertainment, Media Factory, NIS America, Inc., Studio Saki Makura + http://cdn.myanimelist.net/images/anime/13/34043.jpg + Brave 10 + + + 1 + 12021 + 6645 + Studio Deen, DAX Production, Dream Creation, Möbius Tone + http://cdn.myanimelist.net/images/anime/11/35455.jpg + Poyopoyo Kansatsu Nikki + + + 1 + 10447 + 6185 + Satelight, FUNimation Entertainment, Media Factory, Bandai, 8bit + http://cdn.myanimelist.net/images/anime/9/42805.jpg + Aquarion Evol + + + 1 + 11843 + 6616 + Sunrise, TV Tokyo, Square Enix, NIS America, Inc., Trinity Sound + http://cdn.myanimelist.net/images/anime/3/33257.jpg + Danshi Koukousei no Nichijou + + + 1 + 11111 + 6462 + Lantis, Kadokawa Shoten, P.A. Works, Toho Company, Sentai Filmworks, The Klock Worx, Bandai Namco Live Creative, NTT Docomo + http://cdn.myanimelist.net/images/anime/6/41865.jpg + Another + + + 1 + 11433 + 6508 + J.C. Staff, Geneon Universal Entertainment, Genco, AT-X, Sentai Filmworks, Bushiroad Inc., Showgate + http://cdn.myanimelist.net/images/anime/10/34453.jpg + Ano Natsu de Matteru + + + 6 + 15653 + + + http://cdn.myanimelist.net/images/anime/10/42521.jpg + Stay The Same + + + 6 + 15453 + 7259 + Diomedea + http://cdn.myanimelist.net/images/anime/8/41945.jpg + Shinryaku! Ika Musume: Kore ga Umi e no Ai Jana-ika! + + + 1 + 11179 + 6469 + Feel, Starchild Records, Sentai Filmworks, The Klock Worx, PPP, Studio Mausu + http://cdn.myanimelist.net/images/anime/3/41269.jpg + Papa no Iukoto wo Kikinasai! + + + 3 + 18491 + + + http://cdn.myanimelist.net/images/anime/8/49607.jpg + Xi Yang Yang Yu Hui Tai Lang: Zhi Kaixin Chuang Long Nian + + + 6 + 12683 + 6790 + U/M/A/A Inc. + http://cdn.myanimelist.net/images/anime/12/35615.jpg + Yumeyume + + + 1 + 11013 + 6439 + Aniplex, Square Enix, Mainichi Broadcasting, Movic, David Production, Sentai Filmworks, Inu x Boku SS Production Partners + http://cdn.myanimelist.net/images/anime/12/35893.jpg + Inu x Boku SS + + + 1 + 12321 + 6705 + Gonzo, Dentsu, Fuji TV, Toho Company, DLE, Discotek, Sony Music Entertainment + http://cdn.myanimelist.net/images/anime/5/35635.jpg + Thermae Romae + + + 5 + 12865 + 6830 + + http://cdn.myanimelist.net/images/anime/5/36083.jpg + Ai wa Kat-tun + + + 1 + 12651 + 6779 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/13/35735.jpg + Danball Senki W + + + 2 + 5784 + 4276 + AIC, Media Blasters, KENMedia + http://cdn.myanimelist.net/images/anime/9/34911.jpg + Ai no Kusabi (2012) + + + 3 + 11375 + 6502 + Bandai, CyberConnect2, flying DOG, Asmik Ace Entertainment + http://cdn.myanimelist.net/images/anime/2/35633.jpg + .hack//The Movie: Sekai no Mukou ni + + + 2 + 12643 + 6778 + + http://cdn.myanimelist.net/images/anime/13/35451.jpg + Happening Star + + + 1 + 13207 + 6911 + + http://cdn.myanimelist.net/images/anime/2/37123.jpg + Himitsukessha Taka no Tsume Gaiden: Mukashi no Yoshida-kun + + + 1 + 11769 + 6594 + LMD, Cammot + http://cdn.myanimelist.net/images/anime/11/35659.jpg + Gokujo. + + + 4 + 14049 + 7073 + Manglobe + http://cdn.myanimelist.net/images/anime/3/38799.jpg + Mashiroiro Symphony: Airi ga Anata no Kanojo ni!? + + + 4 + 13561 + 6994 + Production I.G, Aniplex, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/4/37761.jpg + Guilty Crown Specials + + + 2 + 13673 + 7010 + Studio Izena + http://cdn.myanimelist.net/images/anime/9/37951.jpg + Otome Nadeshiko Koi Techou + + + 4 + 12823 + 6822 + J.C. Staff, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/11/36063.jpg + Shakugan no Shana III (Final) Specials + + + 4 + 12921 + 6849 + DAX Production, Takeshobo, Seven + http://cdn.myanimelist.net/images/anime/3/36275.jpg + Morita-san wa Mukuchi. Specials + + + 5 + 13255 + 6925 + Studio 4°C + http://cdn.myanimelist.net/images/anime/5/37107.jpg + Haru wa Kuru + + + 4 + 12503 + 6750 + FUNimation Entertainment, Asread + http://cdn.myanimelist.net/images/anime/2/38423.jpg + Mirai Nikki (TV): Ura Mirai Nikki + + + 4 + 13619 + 7001 + Shaft + http://cdn.myanimelist.net/images/anime/8/37831.jpg + Sayonara Zetsubou Sensei Special Omake + + + 4 + 10469 + 6196 + Shaft + http://cdn.myanimelist.net/images/anime/6/28743.jpg + Sayonara Zetsubou Sensei Special + + + 5 + 13543 + 6990 + + http://cdn.myanimelist.net/images/anime/5/37869.jpg + Nantokashite Alguard + + + 5 + 13541 + 6989 + + http://cdn.myanimelist.net/images/anime/10/37867.jpg + Rui no Masaiban + + + 5 + 12907 + 6846 + + http://cdn.myanimelist.net/images/anime/4/36235.jpg + Sekiei Ayakashi Mangatan + + + 1 + 11285 + 6489 + Aniplex, Dentsu, FUNimation Entertainment, Ordet, SANZIGEN, Ultra Super Pictures, Sony Music Entertainment, Fuji Pacific Music Publishing + http://cdn.myanimelist.net/images/anime/5/53909.jpg + Black★Rock Shooter (TV) + + + 2 + 12599 + 6772 + + http://cdn.myanimelist.net/images/anime/8/35317.jpg + Ijime + + + 2 + 19849 + 7892 + CoMix Wave + http://cdn.myanimelist.net/images/anime/4/52931.jpg + Peeping Life 5.0ch + + + 3 + 10218 + 6084 + Studio 4°C, Viz Media, NYAV Post, Yahoo! Japan + http://cdn.myanimelist.net/images/anime/5/33563.jpg + Berserk: Ougon Jidaihen I - Haou no Tamago + + + 1 + 13167 + 6899 + + http://cdn.myanimelist.net/images/anime/4/36875.jpg + Zoobles! + + + 1 + 12191 + 6687 + Toei Animation + http://cdn.myanimelist.net/images/anime/4/36565.jpg + Smile Precure! + + + 4 + 10638 + 6276 + Shaft, NIS America, Inc. + http://cdn.myanimelist.net/images/anime/3/36709.jpg + Denpa Onna to Seishun Otoko Special + + + 5 + 14235 + 7101 + + http://cdn.myanimelist.net/images/anime/12/39339.jpg + Salad Land + + + 5 + 15657 + 7290 + + http://cdn.myanimelist.net/images/anime/7/45694.jpg + Yuke! Shouei-kun + + + 2 + 11339 + 6495 + Aniplex, Dentsu, Mainichi Broadcasting, Studio Gokumi + http://cdn.myanimelist.net/images/anime/12/35321.jpg + A-Channel: A-Channel+smile + + + 3 + 11705 + 6571 + FUNimation Entertainment, Oxybot + http://cdn.myanimelist.net/images/anime/2/32715.jpg + Dragon Age: Blood Mage no Seisen + + + 4 + 13831 + 7041 + + http://cdn.myanimelist.net/images/anime/9/38511.jpg + Kamiusagi Rope: Valentine Day-hen + + + 2 + 9523 + 5773 + Aniplex, ufotable + http://cdn.myanimelist.net/images/anime/6/31903.jpg + Minori Scramble! + + + 6 + 12637 + 6777 + + http://cdn.myanimelist.net/images/anime/6/40821.jpg + Mo Gyutto Love de Sekkinchuu! + + + 2 + 12437 + 6731 + Aniplex, ufotable + http://cdn.myanimelist.net/images/anime/2/34939.jpg + Yuri Seijin Naoko-san (2012) + + + 2 + 10417 + 6171 + Aniplex, ufotable, Aniplex of America + http://cdn.myanimelist.net/images/anime/9/31905.jpg + Gyo + + + 4 + 10863 + 6389 + Frontier Works, FUNimation Entertainment, White Fox + http://cdn.myanimelist.net/images/anime/7/36531.jpg + Steins;Gate: Oukoubakko no Poriomania + + + 4 + 13757 + 7022 + Manglobe + http://cdn.myanimelist.net/images/anime/9/38265.jpg + Mashiroiro Symphony: The Color of Lovers Picture Drama + + + 2 + 11773 + 6596 + Chaos Project + http://cdn.myanimelist.net/images/anime/9/36361.jpg + To Heart 2: Dungeon Travelers + + + 4 + 13137 + 6889 + Zexcs + http://cdn.myanimelist.net/images/anime/9/36791.jpg + Itsuka Tenma no Kuro Usagi Special + + + 6 + 13281 + + + http://cdn.myanimelist.net/images/anime/5/37387.jpg + FlashBack + + + 4 + 13429 + 6962 + Studio Rikka, Asmik Ace Entertainment, Purple Cow Studio Japan + http://cdn.myanimelist.net/images/anime/3/37569.jpg + Sakasama no Patema: Beginning of the Day + \ No newline at end of file diff --git a/data/db/season/2013_fall.xml b/data/db/season/2013_fall.xml index f67e0138f..7c51a090f 100644 --- a/data/db/season/2013_fall.xml +++ b/data/db/season/2013_fall.xml @@ -1,546 +1,623 @@ - - - - Fall 2013 - 1380467405 - - - 1 - 17549 - Lantis, Media Factory, Silver Link - http://cdn.myanimelist.net/images/anime/2/51581.jpg - Non Non Biyori - - - 4 - 19889 - Production I.G, Starchild Records - http://cdn.myanimelist.net/images/anime/3/54119.jpg - Genshiken Nidaime Specials - - - 1 - 19843 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/12/52925.jpg - Tamagotchi! Miracle Friends - - - 3 - 17269 - Toei Animation - http://cdn.myanimelist.net/images/anime/10/52045.jpg - Captain Harlock - - - 4 - 19671 - Kyoto Animation, Lantis, Animation Do - http://cdn.myanimelist.net/images/anime/3/53643.jpg - Free! Specials - - - 2 - 18745 - VAP - http://cdn.myanimelist.net/images/anime/13/54451.jpg - Chihayafuru 2 OVA - - - 3 - 19535 - DLE - http://cdn.myanimelist.net/images/anime/9/54453.jpg - Takanotsume GO: Utsukushiki Elleair Shoushuu Plus - - - 3 - 19021 - Kyoto Animation - http://cdn.myanimelist.net/images/anime/10/50991.jpg - Takanashi Rikka Kai: Chuunibyou demo Koi ga Shitai! Movie - - - 3 - 15197 - Sunrise, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/7/46265.jpg - Code Geass: Boukoku no Akito 2 - Hikisakareshi Yokuryuu - - - 1 - 19841 - AIC, AIC Plus+ - http://cdn.myanimelist.net/images/anime/2/53807.jpg - Super Seisyun Brothers - - - 1 - 17247 - Genco, Media Factory, Lerche - http://cdn.myanimelist.net/images/anime/7/52801.jpg - Machine-Doll wa Kizutsukanai - - - 5 - 20581 - CoMix Wave - http://cdn.myanimelist.net/images/anime/10/54617.jpg - Passionate Detective Agency - - - 1 - 17513 - Frontier Works, Idea Factory, Zexcs, Showgate - http://cdn.myanimelist.net/images/anime/9/51989.jpg - Diabolik Lovers - - - 2 - 18753 - Brains Base - http://cdn.myanimelist.net/images/anime/11/50107.jpg - Yahari Ore no Seishun Love Comedy wa Machigatteiru. OVA - - - 1 - 19825 - Sunrise, Asatsu DK - http://cdn.myanimelist.net/images/anime/7/52895.jpg - Saikyou Ginga Ultimate Zero: Battle Spirits - - - 6 - 20613 - Bones - http://cdn.myanimelist.net/images/anime/10/54659.jpg - Viva Namida - - - 1 - 18115 - Aniplex, A-1 Pictures, Aniplex of America - http://cdn.myanimelist.net/images/anime/5/54533.jpg - Magi: The Kingdom of Magic - - - 1 - 17895 - J.C. Staff, Genco, Starchild Records - http://cdn.myanimelist.net/images/anime/12/52091.jpg - Golden Time - - - 4 - 19109 - Silver Link - http://cdn.myanimelist.net/images/anime/3/51235.jpg - Fate/kaleid liner Prisma☆Illya Specials - - - 1 - 16067 - Geneon Universal Entertainment, P.A. Works, Rondo Robe - http://cdn.myanimelist.net/images/anime/7/53549.jpg - Nagi no Asukara - - - 1 - 19257 - Studio Deen - http://cdn.myanimelist.net/images/anime/3/52073.jpg - Meganebu! - - - 1 - 18497 - Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/5/52563.jpg - Yozakura Quartet: Hana no Uta - - - 3 - 14807 - ufotable, Notes - http://cdn.myanimelist.net/images/anime/2/52559.jpg - Kara no Kyoukai: Mirai Fukuin - - - 1 - 18679 - Aniplex, Aniplex of America, Trigger - http://cdn.myanimelist.net/images/anime/5/54379.jpg - Kill la Kill - - - 1 - 18001 - Media Factory, A.C.G.T. - http://cdn.myanimelist.net/images/anime/11/52089.jpg - Freezing Vibration - - - 1 - 18411 - Diomedea, Pony Canyon - http://cdn.myanimelist.net/images/anime/8/49313.jpg - Gingitsune - - - 1 - 19755 - Kinema Citrus - http://cdn.myanimelist.net/images/anime/9/52771.jpg - Oshiri Kajiri Mushi 2 - - - 1 - 19315 - Studio Deen, Earth Star Entertainment, Pupa Production Committee - http://cdn.myanimelist.net/images/anime/2/54395.jpg - Pupa - - - 1 - 9479 - Starchild Records, GoHands - http://cdn.myanimelist.net/images/anime/6/51997.jpg - Coppelion - - - 1 - 20033 - TV Tokyo, Starchild Records, SANZIGEN, LIDEN FILMS - http://cdn.myanimelist.net/images/anime/5/53559.jpg - Miss Monochrome - - - 1 - 19871 - Studio Pierrot - http://cdn.myanimelist.net/images/anime/3/53241.jpg - Gaist Crusher - - - 4 - 20159 - Production I.G, Xebec, Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/7/53701.jpg - Pokemon: The Origin - - - 1 - 20181 - Sunrise, TV Tokyo, Bandai Visual, Dentsu - http://cdn.myanimelist.net/images/anime/3/53737.jpg - Aikatsu! 2 - - - 1 - 18153 - Kyoto Animation - http://cdn.myanimelist.net/images/anime/5/53111.jpg - Kyoukai no Kanata - - - 1 - 19703 - Toei Animation - http://cdn.myanimelist.net/images/anime/10/53417.jpg - Kyousou Giga (TV) - - - 1 - 18247 - Lantis, TBS, 8bit, Sony Music Communications, Project IS - http://cdn.myanimelist.net/images/anime/12/49359.jpg - IS: Infinite Stratos 2 - - - 1 - 18277 - Silver Link, Connect - http://cdn.myanimelist.net/images/anime/3/54273.jpg - Strike the Blood - - - 1 - 19369 - Feel, Pony Canyon - http://cdn.myanimelist.net/images/anime/7/54343.jpg - Outbreak Company - - - 1 - 18195 - J.C. Staff, Warner Bros. - http://cdn.myanimelist.net/images/anime/7/48895.jpg - Little Busters!: Refrain - - - 1 - 17265 - Satelight, NHK, NHK Enterprises - http://cdn.myanimelist.net/images/anime/10/54267.jpg - Log Horizon - - - 1 - 18677 - Asread - http://cdn.myanimelist.net/images/anime/13/54389.jpg - Yuusha ni Narenakatta Ore wa Shibushibu Shuushoku wo Ketsui Shimashita. - - - 1 - 18689 - Production I.G, Madhouse Studios, TV Tokyo - http://cdn.myanimelist.net/images/anime/5/54235.jpg - Diamond no Ace - - - 1 - 15651 - Sunrise, NHK, NHK Enterprises - http://cdn.myanimelist.net/images/anime/12/54099.jpg - Phi Brain: Kami no Puzzle 3rd Season - - - 4 - 19759 - - http://cdn.myanimelist.net/images/anime/8/52773.jpg - Ansatsu Kyoushitsu - - - 4 - 19511 - Studio Pierrot - http://cdn.myanimelist.net/images/anime/5/52343.jpg - Naruto: Shippuuden - Jump Super Anime Tour 2013 Special - - - 1 - 18245 - Satelight, Starchild Records - http://cdn.myanimelist.net/images/anime/2/53561.jpg - White Album 2 - - - 1 - 19647 - Madhouse Studios, VAP, MAPPA - http://cdn.myanimelist.net/images/anime/12/52629.jpg - Hajime no Ippo: Rising - - - 1 - 19919 - Tesagure! Production Committee - http://cdn.myanimelist.net/images/anime/2/53431.jpg - Tesagure! Bukatsumono - - - 1 - 11763 - Arms, Movic, Earth Star Entertainment - http://cdn.myanimelist.net/images/anime/11/52633.jpg - Sekai de Ichiban Tsuyoku Naritai! - - - 1 - 20473 - MAPPA, Earth Star Entertainment - http://cdn.myanimelist.net/images/anime/2/54455.jpg - Teekyuu 3 - - - 1 - 16894 - Production I.G - http://cdn.myanimelist.net/images/anime/13/52265.jpg - Kuroko no Basket 2 - - - 1 - 19319 - Sunrise, TV Tokyo, Sotsu Agency - http://cdn.myanimelist.net/images/anime/7/51815.jpg - Gundam Build Fighters - - - 1 - 19151 - Lantis, Pony Canyon, 8bit - http://cdn.myanimelist.net/images/anime/7/54377.jpg - Walkure Romanze - - - 1 - 18179 - TMS Entertainment, Toho Company - http://cdn.myanimelist.net/images/anime/5/53211.jpg - Yowamushi Pedal - - - 1 - 18893 - SANZIGEN - http://cdn.myanimelist.net/images/anime/13/50625.jpg - Aoki Hagane no Arpeggio: Ars Nova - - - 2 - 18499 - - http://cdn.myanimelist.net/images/anime/3/49489.jpg - Yozakura Quartet: Tsuki ni Naku - - - 1 - 18767 - Lantis, Hoods Entertainment, Studio Mausu, teamKG - http://cdn.myanimelist.net/images/anime/2/50159.jpg - BlazBlue: Alter Memory - - - 1 - 16011 - 8bit - http://cdn.myanimelist.net/images/anime/5/53229.jpg - Tokyo Ravens - - - 1 - 19221 - Diomedea, DAX Production, Studio Jack, Mages - http://cdn.myanimelist.net/images/anime/10/53235.jpg - Ore no Nounai Sentakushi ga, Gakuen Love Comedy wo Zenryoku de Jama Shiteiru - - - 1 - 20329 - The Klock Worx, Studio Mausu, Opera House - http://cdn.myanimelist.net/images/anime/2/54067.jpg - Koroshiya-san: The Hired Gun - - - 1 - 18295 - Sunrise, Aniplex, Aniplex of America - http://cdn.myanimelist.net/images/anime/8/54439.jpg - Kakumeiki Valvrave 2nd Season - - - 1 - 19365 - Aniplex, Manglobe, Fuji TV, Aniplex of America - http://cdn.myanimelist.net/images/anime/9/53817.jpg - Samurai Flamenco - - - 1 - 19367 - Aniplex, A-1 Pictures, Fuji TV - http://cdn.myanimelist.net/images/anime/11/53239.jpg - Galilei Donna - - - 3 - 20671 - Studio Colorido - http://cdn.myanimelist.net/images/anime/5/54725.jpg - Shashinkan - - - 3 - 20673 - Studio Colorido - http://cdn.myanimelist.net/images/anime/4/54727.jpg - Hinata no Aoshigure - - - 2 - 20039 - J.C. Staff, Pony Canyon - http://cdn.myanimelist.net/images/anime/10/53427.jpg - Kill Me Baby OVA - - - 1 - 19291 - TV Tokyo, Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/12/54549.jpg - Pokemon XY - - - 2 - 18359 - Ordet - http://cdn.myanimelist.net/images/anime/6/49149.jpg - Miyakawa-ke no Kuufuku OVA - - - 3 - 11981 - Aniplex, Shaft, Mainichi Broadcasting, Movic, Nitroplus, Aniplex of America, Madoka Partners, Hobunsha - http://cdn.myanimelist.net/images/anime/3/52585.jpg - Mahou Shoujo Madoka★Magica Movie 3: Hangyaku no Monogatari - - - 3 - 17357 - Toei Animation - http://cdn.myanimelist.net/images/anime/7/51775.jpg - DokiDoki! Precure Movie: Mana Kekkon!!? Mirai ni Tsunagu Kibou no Dress - - - 2 - 20045 - Lantis, 8bit - http://cdn.myanimelist.net/images/anime/2/54323.jpg - IS: Infinite Stratos 2 - Hitonatsu no Omoide - - - 4 - 20141 - TMS Entertainment - http://cdn.myanimelist.net/images/anime/4/53665.jpg - Lupin III: Princess of the Breeze - Kakusareta Kuuchuu Toshi - - - 3 - 16664 - Studio Ghibli - http://cdn.myanimelist.net/images/anime/4/46511.jpg - Kaguya-hime no Monogatari - - - 3 - 20543 - Gonzo, Pony Canyon - http://cdn.myanimelist.net/images/anime/5/54567.jpg - Bayonetta: Bloody Fate - - - 3 - 14407 - AIC A.S.T.A. - http://cdn.myanimelist.net/images/anime/11/47949.jpg - Persona 3 the Movie - - - 2 - 17739 - Shaft - http://cdn.myanimelist.net/images/anime/6/47273.jpg - Hidamari Sketch: Sae Hiro Sotsugyou-hen - - - 3 - 19191 - Production I.G, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/8/52135.jpg - Ghost in the Shell: Arise - Border:2 Ghost Whispers - + + + + Fall 2013 + 1380467405 + + + 1 + 17549 + 7711 + Lantis, Media Factory, Silver Link + http://cdn.myanimelist.net/images/anime/2/51581.jpg + Non Non Biyori + + + 4 + 19889 + 8004 + Production I.G, Starchild Records + http://cdn.myanimelist.net/images/anime/3/54119.jpg + Genshiken Nidaime Specials + + + 1 + 19843 + 7990 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/12/52925.jpg + Tamagotchi! Miracle Friends + + + 3 + 17269 + 7624 + Toei Animation + http://cdn.myanimelist.net/images/anime/10/52045.jpg + Captain Harlock + + + 4 + 19671 + 7913 + Kyoto Animation, Lantis, Animation Do + http://cdn.myanimelist.net/images/anime/3/53643.jpg + Free! Specials + + + 2 + 18745 + 7814 + VAP + http://cdn.myanimelist.net/images/anime/13/54451.jpg + Chihayafuru 2 OVA + + + 3 + 19535 + + DLE + http://cdn.myanimelist.net/images/anime/9/54453.jpg + Takanotsume GO: Utsukushiki Elleair Shoushuu Plus + + + 3 + 19021 + 7773 + Kyoto Animation + http://cdn.myanimelist.net/images/anime/8/58069.jpg + Takanashi Rikka Kai: Chuunibyou demo Koi ga Shitai! Movie + + + 3 + 15197 + 7227 + Sunrise, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/7/46265.jpg + Code Geass: Boukoku no Akito 2 - Hikisakareshi Yokuryuu + + + 1 + 19841 + 7893 + AIC, AIC Plus+ + http://cdn.myanimelist.net/images/anime/2/53807.jpg + Super Seisyun Brothers + + + 1 + 17247 + 7617 + Genco, Media Factory, Lerche + http://cdn.myanimelist.net/images/anime/7/52801.jpg + Machine-Doll wa Kizutsukanai + + + 5 + 20581 + + CoMix Wave + http://cdn.myanimelist.net/images/anime/10/54617.jpg + Passionate Detective Agency + + + 1 + 17513 + 7707 + Frontier Works, Idea Factory, Zexcs, Showgate + http://cdn.myanimelist.net/images/anime/9/51989.jpg + Diabolik Lovers + + + 2 + 18753 + 7748 + Brains Base + http://cdn.myanimelist.net/images/anime/11/50107.jpg + Yahari Ore no Seishun Love Comedy wa Machigatteiru. OVA + + + 1 + 19825 + 7977 + Sunrise, Asatsu DK + http://cdn.myanimelist.net/images/anime/7/52895.jpg + Saikyou Ginga Ultimate Zero: Battle Spirits + + + 6 + 20613 + + Bones + http://cdn.myanimelist.net/images/anime/10/54659.jpg + Viva Namida + + + 1 + 18115 + 7696 + Aniplex, A-1 Pictures, Aniplex of America + http://cdn.myanimelist.net/images/anime/5/54533.jpg + Magi: The Kingdom of Magic + + + 1 + 17895 + 7708 + J.C. Staff, Genco, Starchild Records + http://cdn.myanimelist.net/images/anime/12/52091.jpg + Golden Time + + + 4 + 19109 + 7858 + Silver Link + http://cdn.myanimelist.net/images/anime/3/51235.jpg + Fate/kaleid liner Prisma☆Illya Specials + + + 1 + 16067 + 7370 + Geneon Universal Entertainment, P.A. Works, Rondo Robe + http://cdn.myanimelist.net/images/anime/7/53549.jpg + Nagi no Asukara + + + 1 + 19257 + 7851 + Studio Deen + http://cdn.myanimelist.net/images/anime/3/52073.jpg + Meganebu! + + + 1 + 18497 + 7843 + Tatsunoko Productions + http://cdn.myanimelist.net/images/anime/5/52563.jpg + Yozakura Quartet: Hana no Uta + + + 3 + 14807 + 7167 + ufotable, Notes + http://cdn.myanimelist.net/images/anime/2/52559.jpg + Kara no Kyoukai: Mirai Fukuin + + + 1 + 18679 + 7712 + Aniplex, Aniplex of America, Trigger + http://cdn.myanimelist.net/images/anime/5/54379.jpg + Kill la Kill + + + 1 + 18001 + 7710 + Media Factory, A.C.G.T. + http://cdn.myanimelist.net/images/anime/11/52089.jpg + Freezing Vibration + + + 1 + 18411 + 7703 + Diomedea, Pony Canyon + http://cdn.myanimelist.net/images/anime/8/49313.jpg + Gingitsune + + + 1 + 19755 + 7976 + Kinema Citrus + http://cdn.myanimelist.net/images/anime/9/52771.jpg + Oshiri Kajiri Mushi 2 + + + 1 + 19315 + 7845 + Studio Deen, Earth Star Entertainment, Pupa Production Committee + http://cdn.myanimelist.net/images/anime/2/54395.jpg + Pupa + + + 1 + 9479 + 5746 + Starchild Records, GoHands + http://cdn.myanimelist.net/images/anime/6/51997.jpg + Coppelion + + + 1 + 20033 + 7911 + TV Tokyo, Starchild Records, SANZIGEN, LIDEN FILMS + http://cdn.myanimelist.net/images/anime/5/53559.jpg + Miss Monochrome + + + 1 + 19871 + 7974 + Studio Pierrot + http://cdn.myanimelist.net/images/anime/3/53241.jpg + Gaist Crusher + + + 4 + 20159 + 7922 + Production I.G, Xebec, Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/7/53701.jpg + Pokemon: The Origin + + + 1 + 20181 + 7972 + Sunrise, TV Tokyo, Bandai Visual, Dentsu + http://cdn.myanimelist.net/images/anime/3/53737.jpg + Aikatsu! 2 + + + 1 + 18153 + 7714 + Kyoto Animation + http://cdn.myanimelist.net/images/anime/5/53111.jpg + Kyoukai no Kanata + + + 1 + 19703 + 7870 + Toei Animation + http://cdn.myanimelist.net/images/anime/10/53417.jpg + Kyousou Giga (TV) + + + 1 + 18247 + 7733 + Lantis, TBS, 8bit, Sony Music Communications, Project IS + http://cdn.myanimelist.net/images/anime/12/49359.jpg + IS: Infinite Stratos 2 + + + 1 + 18277 + 7715 + Silver Link, Connect + http://cdn.myanimelist.net/images/anime/3/54273.jpg + Strike the Blood + + + 1 + 19369 + 7837 + Feel, Pony Canyon + http://cdn.myanimelist.net/images/anime/7/54343.jpg + Outbreak Company + + + 1 + 18195 + 7704 + J.C. Staff, Warner Bros. + http://cdn.myanimelist.net/images/anime/7/48895.jpg + Little Busters!: Refrain + + + 1 + 17265 + 7622 + Satelight, NHK, NHK Enterprises + http://cdn.myanimelist.net/images/anime/10/54267.jpg + Log Horizon + + + 1 + 18677 + 7725 + Asread + http://cdn.myanimelist.net/images/anime/13/54389.jpg + Yuusha ni Narenakatta Ore wa Shibushibu Shuushoku wo Ketsui Shimashita. + + + 1 + 18689 + 7699 + Production I.G, Madhouse Studios, TV Tokyo + http://cdn.myanimelist.net/images/anime/5/54235.jpg + Diamond no Ace + + + 1 + 15651 + 7288 + Sunrise, NHK, NHK Enterprises + http://cdn.myanimelist.net/images/anime/12/54099.jpg + Phi Brain: Kami no Puzzle 3rd Season + + + 4 + 19759 + 7973 + Brains Base + http://cdn.myanimelist.net/images/anime/8/52773.jpg + Ansatsu Kyoushitsu + + + 4 + 19511 + 7975 + Studio Pierrot + http://cdn.myanimelist.net/images/anime/5/52343.jpg + Naruto: Shippuuden - Jump Super Anime Tour 2013 Special + + + 1 + 18245 + 7697 + Satelight, Starchild Records + http://cdn.myanimelist.net/images/anime/2/53561.jpg + White Album 2 + + + 1 + 19647 + 7855 + Madhouse Studios, VAP, MAPPA + http://cdn.myanimelist.net/images/anime/12/52629.jpg + Hajime no Ippo: Rising + + + 1 + 19919 + 7912 + Tesagure! Production Committee + http://cdn.myanimelist.net/images/anime/8/56211.jpg + Tesagure! Bukatsumono + + + 1 + 11763 + 6592 + Arms, Movic, Earth Star Entertainment + http://cdn.myanimelist.net/images/anime/11/52633.jpg + Sekai de Ichiban Tsuyoku Naritai! + + + 1 + 20473 + 7980 + MAPPA, Earth Star Entertainment + http://cdn.myanimelist.net/images/anime/2/54455.jpg + Teekyuu 3 + + + 1 + 16894 + 7545 + Production I.G + http://cdn.myanimelist.net/images/anime/13/52265.jpg + Kuroko no Basket 2 + + + 1 + 19319 + 7852 + Sunrise, TV Tokyo, Sotsu Agency + http://cdn.myanimelist.net/images/anime/7/51815.jpg + Gundam Build Fighters + + + 1 + 19151 + 7849 + Lantis, Pony Canyon, 8bit + http://cdn.myanimelist.net/images/anime/7/54377.jpg + Walkure Romanze + + + 1 + 18179 + 7700 + TMS Entertainment, Toho Company + http://cdn.myanimelist.net/images/anime/5/53211.jpg + Yowamushi Pedal + + + 1 + 18893 + 7769 + SANZIGEN + http://cdn.myanimelist.net/images/anime/13/50625.jpg + Aoki Hagane no Arpeggio: Ars Nova + + + 2 + 18499 + 7702 + Tatsunoko Productions + http://cdn.myanimelist.net/images/anime/3/49489.jpg + Yozakura Quartet: Tsuki ni Naku + + + 1 + 18767 + 7722 + Lantis, Hoods Entertainment, Studio Mausu, teamKG + http://cdn.myanimelist.net/images/anime/2/50159.jpg + BlazBlue: Alter Memory + + + 1 + 16011 + 7359 + 8bit + http://cdn.myanimelist.net/images/anime/5/53229.jpg + Tokyo Ravens + + + 1 + 19221 + 7846 + Diomedea, DAX Production, Studio Jack, Mages + http://cdn.myanimelist.net/images/anime/10/53235.jpg + Ore no Nounai Sentakushi ga, Gakuen Love Comedy wo Zenryoku de Jama Shiteiru + + + 1 + 20329 + 8025 + The Klock Worx, Studio Mausu, Opera House + http://cdn.myanimelist.net/images/anime/2/54067.jpg + Koroshiya-san: The Hired Gun + + + 1 + 18295 + 7698 + Sunrise, Aniplex, Aniplex of America + http://cdn.myanimelist.net/images/anime/8/54439.jpg + Kakumeiki Valvrave 2nd Season + + + 1 + 19365 + 7835 + Aniplex, Manglobe, Fuji TV, Aniplex of America + http://cdn.myanimelist.net/images/anime/9/53817.jpg + Samurai Flamenco + + + 1 + 19367 + 7836 + Aniplex, A-1 Pictures, Fuji TV + http://cdn.myanimelist.net/images/anime/11/53239.jpg + Galilei Donna + + + 3 + 20671 + 8085 + Studio Colorido + http://cdn.myanimelist.net/images/anime/5/54725.jpg + Shashinkan + + + 3 + 20673 + 8080 + Studio Colorido + http://cdn.myanimelist.net/images/anime/4/54727.jpg + Hinata no Aoshigure + + + 2 + 20039 + 7900 + J.C. Staff, Pony Canyon + http://cdn.myanimelist.net/images/anime/10/53427.jpg + Kill Me Baby OVA + + + 1 + 19291 + 7850 + TV Tokyo, Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/12/54549.jpg + Pokemon XY + + + 2 + 18359 + 7701 + Ordet + http://cdn.myanimelist.net/images/anime/6/49149.jpg + Miyakawa-ke no Kuufuku OVA + + + 3 + 11981 + 6638 + Aniplex, Shaft, Mainichi Broadcasting, Movic, Nitroplus, Aniplex of America, Madoka Partners, Hobunsha + http://cdn.myanimelist.net/images/anime/3/52585.jpg + Mahou Shoujo Madoka★Magica Movie 3: Hangyaku no Monogatari + + + 3 + 17357 + 7857 + Toei Animation + http://cdn.myanimelist.net/images/anime/7/51775.jpg + DokiDoki! Precure Movie: Mana Kekkon!!? Mirai ni Tsunagu Kibou no Dress + + + 2 + 20045 + 7909 + Lantis, 8bit + http://cdn.myanimelist.net/images/anime/2/54323.jpg + IS: Infinite Stratos 2 - Hitonatsu no Omoide + + + 4 + 20141 + 7923 + TMS Entertainment + http://cdn.myanimelist.net/images/anime/4/53665.jpg + Lupin III: Princess of the Breeze - Kakusareta Kuuchuu Toshi + + + 3 + 16664 + 7486 + Studio Ghibli + http://cdn.myanimelist.net/images/anime/4/46511.jpg + Kaguya-hime no Monogatari + + + 3 + 20543 + 8024 + Gonzo, Pony Canyon + http://cdn.myanimelist.net/images/anime/5/54567.jpg + Bayonetta: Bloody Fate + + + 3 + 14407 + 7124 + AIC A.S.T.A. + http://cdn.myanimelist.net/images/anime/11/47949.jpg + Persona 3 the Movie + + + 2 + 17739 + 8021 + Shaft + http://cdn.myanimelist.net/images/anime/6/47273.jpg + Hidamari Sketch: Sae Hiro Sotsugyou-hen + + + 3 + 19191 + 7856 + Production I.G, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/8/52135.jpg + Ghost in the Shell: Arise - Border:2 Ghost Whispers + \ No newline at end of file diff --git a/data/db/season/2013_spring.xml b/data/db/season/2013_spring.xml index 05f9aa83e..1e35f8f2d 100644 --- a/data/db/season/2013_spring.xml +++ b/data/db/season/2013_spring.xml @@ -1,791 +1,903 @@ - - - - Spring 2013 - 1380416484 - - - 1 - 17141 - NOTTV - http://cdn.myanimelist.net/images/anime/11/45801.jpg - Kara The Animation - - - 3 - 14347 - Gonzo - http://cdn.myanimelist.net/images/anime/9/42991.jpg - Ryo - - - 3 - 14349 - Trigger - http://cdn.myanimelist.net/images/anime/2/42989.jpg - Little Witch Academia - - - 3 - 14353 - Madhouse Studios - http://cdn.myanimelist.net/images/anime/11/48721.jpg - Death Billiards - - - 3 - 13863 - Zexcs - http://cdn.myanimelist.net/images/anime/4/43343.jpg - Arve Rezzle: Kikaijikake no Yoseitachi - - - 6 - 17901 - 8bit, Sony Music Entertainment - http://cdn.myanimelist.net/images/anime/6/47703.jpg - All Alone With You - - - 4 - 17819 - Kyoto Animation, Lantis, Rakuonsha, Animation Do - http://cdn.myanimelist.net/images/anime/4/51881.jpg - Kyoto Animation: Suiei-hen - - - 4 - 17743 - Arms, Genco, Kadokawa Shoten, Animax, Pony Canyon, Movic, Enterbrain, flying DOG - http://cdn.myanimelist.net/images/anime/2/47281.jpg - Maoyuu Maou Yuusha: Kono Monogatari wa, Daniku Dake Dewanai no ja! - - - 3 - 14175 - Lantis, P.A. Works, NIS America, Inc. - http://cdn.myanimelist.net/images/anime/3/49267.jpg - Hanasaku Iroha: Home Sweet Home - - - 5 - 17397 - - http://cdn.myanimelist.net/images/anime/8/47377.jpg - Cyclops Shoujo Saipuu - - - 2 - 15933 - Production I.G - http://cdn.myanimelist.net/images/anime/3/43213.jpg - Vassalord. - - - 3 - 18943 - - http://cdn.myanimelist.net/images/anime/6/50739.jpg - Shimajirou to Fufu no Daibouken Movie: Sukue! Nana-iro no Hana - - - 3 - 16442 - Toei Animation - http://cdn.myanimelist.net/images/anime/2/44386.jpg - Precure All Stars New Stage 2: Kokoro no Tomodachi - - - 4 - 17879 - Kyoto Animation - http://cdn.myanimelist.net/images/anime/12/47771.jpg - Tamako Market Specials - - - 6 - 17897 - MAPPA - http://cdn.myanimelist.net/images/anime/5/47699.jpg - Hana wa Saku - - - 6 - 17913 - Poncotan - http://cdn.myanimelist.net/images/anime/10/47733.jpg - Redial - - - 2 - 20453 - Liverpool - http://cdn.myanimelist.net/images/anime/6/54301.jpg - Mikosuri Han-Gekijou - - - 4 - 16738 - SANZIGEN - http://cdn.myanimelist.net/images/anime/10/45276.jpg - Wooser no Sono Higurashi: Ken to Pantsu to Wooser to - - - 2 - 15411 - J.C. Staff, Frontier Works, Movic, Warner Bros., Showgate - http://cdn.myanimelist.net/images/anime/10/41819.jpg - Arcana Famiglia: Capriccio - stile Arcana Famiglia - - - 4 - 18781 - Gathering - http://cdn.myanimelist.net/images/anime/12/50157.jpg - Puchimas!: Petit iDOLM@STER - Takatsuki Gold Densetsu Special!! Harukasan Matsuri - - - 4 - 18045 - Aniplex, AIC Build - http://cdn.myanimelist.net/images/anime/13/47955.jpg - Koi to Senkyo to Chocolate Special - - - 2 - 15279 - Silver Link - http://cdn.myanimelist.net/images/anime/13/41237.jpg - Kira Kira 5th Anniversary Live Anime: Kick Start Generation - - - 4 - 17345 - Artland, Genco, Media Factory, Marvelous AQL, Senran Kagura Partners - http://cdn.myanimelist.net/images/anime/8/46997.jpg - Senran Kagura Specials - - - 2 - 15609 - Lantis, TBS, DAX Production, Studio Gokumi - http://cdn.myanimelist.net/images/anime/11/51459.jpg - Kono Naka ni Hitori, Imouto ga Iru!: Ani, Imouto, Koibito - - - 2 - 16928 - Bandai Visual, Lantis, Hiiro No Kakera Production Committee - http://cdn.myanimelist.net/images/anime/9/47967.jpg - Hiiro no Kakera: Totsugeki! Tonari no Ikemenzu - - - 2 - 14893 - Feel - http://cdn.myanimelist.net/images/anime/11/51565.jpg - Dakara Boku wa, H ga Dekinai. OVA - - - 4 - 18231 - Arms, Genco, Hoods Entertainment - http://cdn.myanimelist.net/images/anime/8/48913.jpg - Vanquished Queens Specials - - - 6 - 20365 - Lantis, P.A. Works - http://cdn.myanimelist.net/images/anime/11/54111.jpg - Another: Misaki Mei - Shizuka ni - - - 2 - 16363 - Arms, Genco, Hoods Entertainment - http://cdn.myanimelist.net/images/anime/12/48917.jpg - Vanquished Queens - - - 3 - 14837 - Toei Animation - http://cdn.myanimelist.net/images/anime/9/44490.jpg - Dragon Ball Z Movie 14: Kami to Kami - - - 4 - 18149 - DAX Production, Hotline - http://cdn.myanimelist.net/images/anime/9/49299.jpg - Ishida to Asakura Special - - - 5 - 18205 - CoMix Wave - http://cdn.myanimelist.net/images/anime/3/48867.jpg - World Fool News - - - 1 - 18137 - TV Tokyo - http://cdn.myanimelist.net/images/anime/12/48709.jpg - Train Heroes - - - 1 - 17705 - TV Tokyo, Asia-Do - http://cdn.myanimelist.net/images/anime/12/50323.jpg - DD Hokuto no Ken (2013) - - - 1 - 18155 - Shogakukan Music & Digital Entertainment - http://cdn.myanimelist.net/images/anime/10/50679.jpg - Linetown - - - 1 - 17917 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/6/47735.jpg - Danball Senki Wars - - - 1 - 14921 - FUNimation Entertainment, Lantis, P.A. Works - http://cdn.myanimelist.net/images/anime/4/50313.jpg - RDG: Red Data Girl - - - 1 - 16035 - Bandai Visual, Manglobe, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/8/46639.jpg - Karneval (TV) - - - 1 - 12711 - A-1 Pictures, Sentai Filmworks, Showgate - http://cdn.myanimelist.net/images/anime/12/44019.jpg - Uta no☆Prince-sama♪ Maji Love 2000% - - - 1 - 15809 - FUNimation Entertainment, Lantis, Pony Canyon, White Fox - http://cdn.myanimelist.net/images/anime/3/50177.jpg - Hataraku Maou-sama! - - - 1 - 16355 - Media Factory, Sentai Filmworks, Studio Gokumi - http://cdn.myanimelist.net/images/anime/10/52137.jpg - Dansai Bunri no Crime Edge - - - 1 - 15863 - Dogakobo, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/10/44594.jpg - Ginga Kikoutai Majestic Prince - - - 1 - 16397 - Madhouse Studios, TBS, Sentai Filmworks, Enterbrain, BS-TBS - http://cdn.myanimelist.net/images/anime/11/49199.jpg - Photokano - - - 1 - 15377 - Arms, Genco, Lantis, Sentai Filmworks, Hobby Japan - http://cdn.myanimelist.net/images/anime/5/45248.jpg - Hyakka Ryouran: Samurai Bride - - - 1 - 18241 - DLE - http://cdn.myanimelist.net/images/anime/3/49315.jpg - Himitsukessha Taka no Tsume MAX - - - 1 - 16201 - Starchild Records, Zexcs, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/8/50559.jpg - Aku no Hana - - - 1 - 16512 - Mainichi Broadcasting, Pony Canyon, Sentai Filmworks, Bridge, Devil Survivor 2 Animation Committee, Index - http://cdn.myanimelist.net/images/anime/11/47191.jpg - Devil Survivor 2 The Animation - - - 1 - 14813 - Brains Base, TBS, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/11/49459.jpg - Yahari Ore no Seishun Love Comedy wa Machigatteiru. - - - 1 - 18191 - Graphinica - http://cdn.myanimelist.net/images/anime/5/49745.jpg - Boku wa Ou-sama - - - 1 - 17681 - Gonzo, TV Tokyo, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/13/47499.jpg - Zettai Bouei Leviathan - - - 1 - 17703 - NHK, NHK Enterprises, Shogakukan Music & Digital Entertainment - http://cdn.myanimelist.net/images/anime/9/47209.jpg - Danchi Tomoo - - - 1 - 17969 - TV Tokyo, Toei Animation - http://cdn.myanimelist.net/images/anime/13/48825.jpg - Tanken Driland: 1000-nen no Mahou - - - 1 - 18227 - Production Reed - http://cdn.myanimelist.net/images/anime/5/51759.jpg - Bakujuu Gasshin Ziguru Hazeru - - - 1 - 17727 - Ryukyu Asahi Broadcasting - http://cdn.myanimelist.net/images/anime/7/47233.jpg - Haitai Nanafa 2nd Season - - - 1 - 17497 - TV Tokyo, Studio Comet, Sanrio - http://cdn.myanimelist.net/images/anime/5/47697.jpg - Jewelpet Happiness - - - 1 - 17249 - TV Tokyo, Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/8/46049.jpg - Pretty Rhythm: Rainbow Live - - - 4 - 18599 - J.C. Staff - http://cdn.myanimelist.net/images/anime/10/49925.jpg - Chokotan! - - - 1 - 18391 - DLE - http://cdn.myanimelist.net/images/anime/3/49225.jpg - Mini Vanguard - - - 1 - 15583 - FUNimation Entertainment, AIC Plus+, Nippon Columbia - http://cdn.myanimelist.net/images/anime/13/44844.jpg - Date A Live - - - 1 - 19445 - Fuji TV, Eiken - http://cdn.myanimelist.net/images/anime/8/52129.jpg - Tetsujin 28-gou Gao! - - - 1 - 16910 - Starchild Records, Tatsunoko Productions, Studio Tulip, Half H.P Studio - http://cdn.myanimelist.net/images/anime/5/47379.jpg - Namiuchigiwa no Muromi-san - - - 1 - 17707 - DLE - http://cdn.myanimelist.net/images/anime/11/47215.jpg - Glass no Kamen Desu ga - - - 1 - 17731 - Earth Star Entertainment - http://cdn.myanimelist.net/images/anime/8/49273.jpg - Ketsuekigata-kun! - - - 1 - 17733 - Dibetagurashi Production Committee - http://cdn.myanimelist.net/images/anime/8/50677.jpg - Dibetagurashi: Ahiru no Seikatsu - - - 1 - 13659 - Aniplex, A-1 Pictures, Aniplex of America - http://cdn.myanimelist.net/images/anime/8/45769.jpg - Ore no Imouto ga Konnani Kawaii Wake ga Nai. - - - 1 - 16241 - Production I.G - http://cdn.myanimelist.net/images/anime/3/48957.jpg - Yondemasu yo, Azazel-san. Z - - - 1 - 16498 - Production I.G, FUNimation Entertainment, Mainichi Broadcasting, Pony Canyon, Kodansha, Mad Box, Wit Studio, Shingeki no Kyojin Team - http://cdn.myanimelist.net/images/anime/10/47347.jpg - Shingeki no Kyojin - - - 1 - 16524 - Production I.G, Bandai Visual, Yomiuri Telecasting Corporation, Lantis, Viz Media, Nitroplus, Bushiroad Inc. - http://cdn.myanimelist.net/images/anime/11/48817.jpg - Suisei no Gargantia - - - 1 - 19877 - Sunrise - http://cdn.myanimelist.net/images/anime/7/52985.jpg - Battle Spirits: Sword Eyes Gekitouden - - - 1 - 15699 - Xebec - http://cdn.myanimelist.net/images/anime/10/47533.jpg - Haiyore! Nyaruko-san W - - - 1 - 17505 - Seven Arcs - http://cdn.myanimelist.net/images/anime/12/48827.jpg - Mushibugyou - - - 1 - 18365 - Nippon Television Network Corporation, Oddjob - http://cdn.myanimelist.net/images/anime/8/49161.jpg - Mazinger ZIP! - - - 1 - 16982 - TV Tokyo, Manglobe - http://cdn.myanimelist.net/images/anime/10/48953.jpg - Hayate no Gotoku! Cuties - - - 1 - 16518 - Satelight, Lantis, JM animation - http://cdn.myanimelist.net/images/anime/9/47011.jpg - Arata Kangatari - - - 1 - 17849 - DAX Production, Dream Creation, Hotline - http://cdn.myanimelist.net/images/anime/3/50223.jpg - Sparrow's Hotel - - - 1 - 15911 - Kinema Citrus, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/12/48747.jpg - Yuyushiki - - - 1 - 17082 - Pony Canyon, Tohokushinsha Film Corporation, LIDEN FILMS - http://cdn.myanimelist.net/images/anime/12/49035.jpg - Aiura - - - 5 - 18413 - Production I.G - http://cdn.myanimelist.net/images/anime/7/49979.jpg - Puchitto Gargantia - - - 5 - 18389 - - http://cdn.myanimelist.net/images/anime/12/49219.jpg - Heisei Policemen!! - - - 1 - 16668 - Sunrise, Aniplex, Aniplex of America - http://cdn.myanimelist.net/images/anime/3/49251.jpg - Kakumeiki Valvrave - - - 3 - 17263 - Group TAC - http://cdn.myanimelist.net/images/anime/5/46099.jpg - Hanakappa Movie: Hana-sake! Pakkaan Chou no Kuni no Daibouken - - - 1 - 16049 - J.C. Staff, FUNimation Entertainment, ASCII Media Works - http://cdn.myanimelist.net/images/anime/9/47547.jpg - Toaru Kagaku no Railgun S - - - 3 - 14669 - Sotsu Agency, AIC A.S.T.A., Lantis, Pony Canyon, Movic, Delphi Sound, Marvelous AQL - http://cdn.myanimelist.net/images/anime/3/50315.jpg - Aura: Maryuuinkouga Saigo no Tatakai - - - 1 - 15225 - J.C. Staff, Frontier Works, Media Factory, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/3/50105.jpg - Hentai Ouji to Warawanai Neko. - - - 3 - 12513 - Xebec, AIC - http://cdn.myanimelist.net/images/anime/13/49181.jpg - Uchuu Senkan Yamato 2199 Movie 5: Boukyou no Gingakan Kuukan - - - 2 - 15847 - Madhouse Studios, Marvel Entertainment - http://cdn.myanimelist.net/images/anime/6/43035.jpg - Iron Man: Rise of Technovore - - - 6 - 19287 - Sony Music Entertainment - http://cdn.myanimelist.net/images/anime/3/51725.jpg - Reunion - - - 4 - 18419 - Charaction - http://cdn.myanimelist.net/images/anime/9/49317.jpg - Boku no Imouto wa "Osaka Okan": Uchi no Onii-chan wa Tokyo Rule - - - 3 - 11577 - Frontier Works, Media Factory, Movic, AT-X, White Fox, Kadokawa Pictures Japan, Mages - http://cdn.myanimelist.net/images/anime/3/50317.jpg - Steins;Gate: Fuka Ryouiki no Déjà vu - - - 3 - 14735 - TMS Entertainment - http://cdn.myanimelist.net/images/anime/4/47419.jpg - Detective Conan Movie 17: Private Eye in the Distant Sea - - - 3 - 17113 - TV Asahi, Asatsu DK, Shin-Ei Animation - http://cdn.myanimelist.net/images/anime/6/45745.jpg - Crayon Shin-chan Movie 21: Bakauma! B-Kyuu Gourmet Survival Battle!! - - - 2 - 17273 - Studio Deen - http://cdn.myanimelist.net/images/anime/12/46525.jpg - Hetalia: The Beautiful World Specials - - - 4 - 17391 - Ordet, LIDEN FILMS - http://cdn.myanimelist.net/images/anime/2/51475.jpg - Senyuu. Specials - - - 5 - 19337 - CoMix Wave - http://cdn.myanimelist.net/images/anime/6/52087.jpg - Momoya x Peeping Life: Go en Desu yo! - - - 1 - 17873 - TV Tokyo, Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/5/49285.jpg - Pokemon Best Wishes! Season 2: Decolora Adventure - - - 2 - 17137 - - http://cdn.myanimelist.net/images/anime/10/45795.jpg - Saiyuuki Gaiden: Tokubetsu-hen - Kouga no Shou - - - 4 - 17635 - P.A. Works - http://cdn.myanimelist.net/images/anime/10/50321.jpg - Koitabi: True Tours Nanto - - - 5 - 17637 - Ordet, Encourage Films - http://cdn.myanimelist.net/images/anime/5/47451.jpg - Miyakawa-ke no Kuufuku - - - 4 - 18723 - Digital Frontier - http://cdn.myanimelist.net/images/anime/11/50479.jpg - Soul Reviver - - - 1 - 18097 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/2/49743.jpg - Inazuma Eleven Go: Galaxy - - - 1 - 18469 - - http://cdn.myanimelist.net/images/anime/7/49447.jpg - Odoriko Clinoppe - - - 3 - 15771 - Aniplex, A-1 Pictures, Kodansha, Toho Company - http://cdn.myanimelist.net/images/anime/7/48723.jpg - Saint☆Onii-san (Movie) - - - 4 - 16436 - AIC Frontier, Flex Comics, G-mode - http://cdn.myanimelist.net/images/anime/9/44348.jpg - Tenshi no Drop - - - 3 - 20383 - T.O Entertainment - http://cdn.myanimelist.net/images/anime/2/54141.jpg - Baka Mukashi Banashi Movie: Jijii Wars - - - 6 - 18799 - NAZ - http://cdn.myanimelist.net/images/anime/6/50237.jpg - Take Your Way - - - 2 - 18835 - - http://cdn.myanimelist.net/images/anime/11/50239.jpg - Hakuouki Reimeiroku Tokuten Disc - - - 4 - 17341 - 8bit, Earth Star Entertainment - http://cdn.myanimelist.net/images/anime/6/46515.jpg - Yama no Susume: Kabette Kowakunai no? - - - 4 - 17351 - Dogakobo, Earth Star Entertainment - http://cdn.myanimelist.net/images/anime/12/46513.jpg - Mangirl!: Asobu Henshuu Girl - - - 3 - 16149 - Production I.G - http://cdn.myanimelist.net/images/anime/3/46509.jpg - Kick-Heart - - - 4 - 19029 - Kinema Citrus - http://cdn.myanimelist.net/images/anime/6/51019.jpg - Yuyushiki Specials - - - 3 - 16782 - CoMix Wave, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/11/50861.jpg - Kotonoha no Niwa - - - 3 - 19687 - Vasoon Animation - http://cdn.myanimelist.net/images/anime/7/52701.jpg - Kuiba II - + + + + Spring 2013 + 1380416484 + + + 1 + 17141 + 7592 + NOTTV + http://cdn.myanimelist.net/images/anime/11/45801.jpg + Kara The Animation + + + 3 + 14347 + 7114 + Gonzo + http://cdn.myanimelist.net/images/anime/9/42991.jpg + Ryo + + + 3 + 14349 + 7115 + Trigger + http://cdn.myanimelist.net/images/anime/2/42989.jpg + Little Witch Academia + + + 3 + 14353 + 7116 + Madhouse Studios + http://cdn.myanimelist.net/images/anime/11/48721.jpg + Death Billiards + + + 3 + 13863 + 7051 + Zexcs + http://cdn.myanimelist.net/images/anime/4/43343.jpg + Arve Rezzle: Kikaijikake no Yoseitachi + + + 6 + 17901 + + 8bit, Sony Music Entertainment + http://cdn.myanimelist.net/images/anime/6/47703.jpg + All Alone With You + + + 4 + 17819 + 7742 + Kyoto Animation, Lantis, Rakuonsha, Animation Do + http://cdn.myanimelist.net/images/anime/4/51881.jpg + Kyoto Animation: Suiei-hen + + + 4 + 17743 + 7828 + Arms, Genco, Kadokawa Shoten, Animax, Pony Canyon, Movic, Enterbrain, flying DOG + http://cdn.myanimelist.net/images/anime/2/47281.jpg + Maoyuu Maou Yuusha: Kono Monogatari wa, Daniku Dake Dewanai no ja! + + + 3 + 14175 + 7090 + Lantis, P.A. Works, NIS America, Inc. + http://cdn.myanimelist.net/images/anime/3/49267.jpg + Hanasaku Iroha: Home Sweet Home + + + 5 + 17397 + 7655 + + http://cdn.myanimelist.net/images/anime/8/47377.jpg + Cyclops Shoujo Saipuu + + + 2 + 15933 + 7343 + Production I.G + http://cdn.myanimelist.net/images/anime/3/43213.jpg + Vassalord. + + + 3 + 18943 + + Benesse Corporation + http://cdn.myanimelist.net/images/anime/6/50739.jpg + Shimajirou to Fufu no Daibouken Movie: Sukue! Nana-iro no Hana + + + 3 + 16442 + 7429 + Toei Animation + http://cdn.myanimelist.net/images/anime/2/44386.jpg + Precure All Stars New Stage 2: Kokoro no Tomodachi + + + 4 + 17879 + 7772 + Kyoto Animation + http://cdn.myanimelist.net/images/anime/12/47771.jpg + Tamako Market Specials + + + 6 + 17897 + + MAPPA + http://cdn.myanimelist.net/images/anime/5/47699.jpg + Hana wa Saku + + + 6 + 17913 + + Poncotan + http://cdn.myanimelist.net/images/anime/10/47733.jpg + Redial + + + 2 + 20453 + + Liverpool + http://cdn.myanimelist.net/images/anime/6/54301.jpg + Mikosuri Han-Gekijou + + + 4 + 16738 + 7502 + SANZIGEN + http://cdn.myanimelist.net/images/anime/10/45276.jpg + Wooser no Sono Higurashi: Ken to Pantsu to Wooser to + + + 2 + 15411 + 7252 + J.C. Staff, Frontier Works, Movic, Warner Bros., Showgate + http://cdn.myanimelist.net/images/anime/10/41819.jpg + Arcana Famiglia: Capriccio - stile Arcana Famiglia + + + 4 + 18781 + 7823 + Gathering + http://cdn.myanimelist.net/images/anime/12/50157.jpg + Puchimas!: Petit iDOLM@STER - Takatsuki Gold Densetsu Special!! Harukasan Matsuri + + + 4 + 18045 + 7737 + Aniplex, AIC Build + http://cdn.myanimelist.net/images/anime/13/47955.jpg + Koi to Senkyo to Chocolate Special + + + 2 + 15279 + 7235 + Silver Link + http://cdn.myanimelist.net/images/anime/13/41237.jpg + Kira Kira 5th Anniversary Live Anime: Kick Start Generation + + + 4 + 17345 + 7818 + Artland, Genco, Media Factory, Marvelous AQL, Senran Kagura Partners + http://cdn.myanimelist.net/images/anime/8/46997.jpg + Senran Kagura Specials + + + 2 + 15609 + 7282 + Lantis, TBS, DAX Production, Studio Gokumi + http://cdn.myanimelist.net/images/anime/11/51459.jpg + Kono Naka ni Hitori, Imouto ga Iru!: Ani, Imouto, Koibito + + + 2 + 16928 + 7554 + Bandai Visual, Lantis, Hiiro No Kakera Production Committee + http://cdn.myanimelist.net/images/anime/9/47967.jpg + Hiiro no Kakera: Totsugeki! Tonari no Ikemenzu + + + 2 + 14893 + 7180 + Feel + http://cdn.myanimelist.net/images/anime/11/51565.jpg + Dakara Boku wa, H ga Dekinai. OVA + + + 4 + 18231 + 7847 + Arms, Genco, Hoods Entertainment + http://cdn.myanimelist.net/images/anime/8/48913.jpg + Vanquished Queens Specials + + + 6 + 20365 + + Lantis, P.A. Works + http://cdn.myanimelist.net/images/anime/6/56377.jpg + Another: Misaki Mei - Shizuka ni + + + 2 + 16363 + 7412 + Arms, Genco, Hoods Entertainment + http://cdn.myanimelist.net/images/anime/12/48917.jpg + Vanquished Queens + + + 3 + 14837 + 7176 + Toei Animation + http://cdn.myanimelist.net/images/anime/9/44490.jpg + Dragon Ball Z Movie 14: Kami to Kami + + + 4 + 18149 + 7809 + DAX Production, Hotline + http://cdn.myanimelist.net/images/anime/9/49299.jpg + Ishida to Asakura Special + + + 5 + 18205 + + CoMix Wave + http://cdn.myanimelist.net/images/anime/3/48867.jpg + World Fool News + + + 1 + 18137 + + TV Tokyo + http://cdn.myanimelist.net/images/anime/12/48709.jpg + Train Heroes + + + 1 + 17705 + 7646 + TV Tokyo, Asia-Do + http://cdn.myanimelist.net/images/anime/12/50323.jpg + DD Hokuto no Ken (2013) + + + 1 + 18155 + + Shogakukan Music & Digital Entertainment + http://cdn.myanimelist.net/images/anime/10/50679.jpg + Linetown + + + 1 + 17917 + 7925 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/6/47735.jpg + Danball Senki Wars + + + 1 + 14921 + 7182 + FUNimation Entertainment, Lantis, P.A. Works + http://cdn.myanimelist.net/images/anime/4/50313.jpg + RDG: Red Data Girl + + + 1 + 16035 + 7364 + Bandai Visual, Manglobe, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/8/46639.jpg + Karneval (TV) + + + 1 + 12711 + 6796 + A-1 Pictures, Sentai Filmworks, Showgate + http://cdn.myanimelist.net/images/anime/12/44019.jpg + Uta no☆Prince-sama♪ Maji Love 2000% + + + 1 + 15809 + 7314 + FUNimation Entertainment, Lantis, Pony Canyon, White Fox + http://cdn.myanimelist.net/images/anime/3/50177.jpg + Hataraku Maou-sama! + + + 1 + 16355 + 7411 + Media Factory, Sentai Filmworks, Studio Gokumi + http://cdn.myanimelist.net/images/anime/10/52137.jpg + Dansai Bunri no Crime Edge + + + 1 + 15863 + 7325 + Dogakobo, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/10/44594.jpg + Ginga Kikoutai Majestic Prince + + + 1 + 16397 + 7421 + Madhouse Studios, TBS, Sentai Filmworks, Enterbrain, BS-TBS + http://cdn.myanimelist.net/images/anime/11/49199.jpg + Photokano + + + 1 + 15377 + 7244 + Arms, Genco, Lantis, Sentai Filmworks, Hobby Japan + http://cdn.myanimelist.net/images/anime/5/45248.jpg + Hyakka Ryouran: Samurai Bride + + + 1 + 18241 + + DLE + http://cdn.myanimelist.net/images/anime/3/49315.jpg + Himitsukessha Taka no Tsume MAX + + + 1 + 16201 + 7387 + Starchild Records, Zexcs, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/8/50559.jpg + Aku no Hana + + + 1 + 16512 + 7446 + Mainichi Broadcasting, Pony Canyon, Sentai Filmworks, Bridge, Devil Survivor 2 Animation Committee, Index + http://cdn.myanimelist.net/images/anime/11/47191.jpg + Devil Survivor 2 The Animation + + + 1 + 14813 + 7169 + Brains Base, TBS, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/11/49459.jpg + Yahari Ore no Seishun Love Comedy wa Machigatteiru. + + + 1 + 18191 + + Graphinica + http://cdn.myanimelist.net/images/anime/5/49745.jpg + Boku wa Ou-sama + + + 1 + 17681 + 7644 + Gonzo, TV Tokyo, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/13/47499.jpg + Zettai Bouei Leviathan + + + 1 + 17703 + 7647 + NHK, NHK Enterprises, Shogakukan Music & Digital Entertainment + http://cdn.myanimelist.net/images/anime/9/47209.jpg + Danchi Tomoo + + + 1 + 17969 + + TV Tokyo, Toei Animation + http://cdn.myanimelist.net/images/anime/13/48825.jpg + Tanken Driland: 1000-nen no Mahou + + + 1 + 18227 + + Production Reed + http://cdn.myanimelist.net/images/anime/5/51759.jpg + Bakujuu Gasshin Ziguru Hazeru + + + 1 + 17727 + 7721 + Ryukyu Asahi Broadcasting + http://cdn.myanimelist.net/images/anime/7/47233.jpg + Haitai Nanafa 2nd Season + + + 1 + 17497 + 7650 + TV Tokyo, Studio Comet, Sanrio + http://cdn.myanimelist.net/images/anime/5/47697.jpg + Jewelpet Happiness + + + 1 + 17249 + 7618 + TV Tokyo, Tatsunoko Productions + http://cdn.myanimelist.net/images/anime/8/46049.jpg + Pretty Rhythm: Rainbow Live + + + 4 + 18599 + 7819 + J.C. Staff + http://cdn.myanimelist.net/images/anime/10/49925.jpg + Chokotan! + + + 1 + 18391 + + DLE + http://cdn.myanimelist.net/images/anime/3/49225.jpg + Mini Vanguard + + + 1 + 15583 + 7278 + FUNimation Entertainment, AIC Plus+, Nippon Columbia + http://cdn.myanimelist.net/images/anime/13/44844.jpg + Date A Live + + + 1 + 19445 + 7876 + Fuji TV, Eiken + http://cdn.myanimelist.net/images/anime/8/52129.jpg + Tetsujin 28-gou Gao! + + + 1 + 16910 + 7549 + Starchild Records, Tatsunoko Productions, Studio Tulip, Half H.P Studio + http://cdn.myanimelist.net/images/anime/5/47379.jpg + Namiuchigiwa no Muromi-san + + + 1 + 17707 + 7649 + DLE + http://cdn.myanimelist.net/images/anime/11/47215.jpg + Glass no Kamen Desu ga + + + 1 + 17731 + 7651 + Earth Star Entertainment + http://cdn.myanimelist.net/images/anime/8/49273.jpg + Ketsuekigata-kun! + + + 1 + 17733 + 7648 + Dibetagurashi Production Committee + http://cdn.myanimelist.net/images/anime/8/50677.jpg + Dibetagurashi: Ahiru no Seikatsu + + + 1 + 13659 + 7006 + Aniplex, A-1 Pictures, Aniplex of America + http://cdn.myanimelist.net/images/anime/8/45769.jpg + Ore no Imouto ga Konnani Kawaii Wake ga Nai. + + + 1 + 16241 + 7391 + Production I.G + http://cdn.myanimelist.net/images/anime/3/48957.jpg + Yondemasu yo, Azazel-san. Z + + + 1 + 16498 + 7442 + Production I.G, FUNimation Entertainment, Mainichi Broadcasting, Pony Canyon, Kodansha, Mad Box, Wit Studio, Shingeki no Kyojin Team + http://cdn.myanimelist.net/images/anime/10/47347.jpg + Shingeki no Kyojin + + + 1 + 16524 + 7449 + Production I.G, Bandai Visual, Yomiuri Telecasting Corporation, Lantis, Viz Media, Nitroplus, Bushiroad Inc. + http://cdn.myanimelist.net/images/anime/11/48817.jpg + Suisei no Gargantia + + + 1 + 19877 + + Sunrise + http://cdn.myanimelist.net/images/anime/7/52985.jpg + Battle Spirits: Sword Eyes Gekitouden + + + 1 + 15699 + 7295 + Xebec + http://cdn.myanimelist.net/images/anime/10/47533.jpg + Haiyore! Nyaruko-san W + + + 1 + 17505 + 7653 + Seven Arcs + http://cdn.myanimelist.net/images/anime/12/48827.jpg + Mushibugyou + + + 1 + 18365 + + Nippon Television Network Corporation, Oddjob + http://cdn.myanimelist.net/images/anime/8/49161.jpg + Mazinger ZIP! + + + 1 + 16982 + 7561 + TV Tokyo, Manglobe + http://cdn.myanimelist.net/images/anime/10/48953.jpg + Hayate no Gotoku! Cuties + + + 1 + 16518 + 7448 + Satelight, Lantis, JM animation + http://cdn.myanimelist.net/images/anime/9/47011.jpg + Arata Kangatari + + + 1 + 17849 + 7654 + DAX Production, Dream Creation, Hotline + http://cdn.myanimelist.net/images/anime/3/50223.jpg + Sparrow's Hotel + + + 1 + 15911 + 7338 + Kinema Citrus, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/12/48747.jpg + Yuyushiki + + + 1 + 17082 + 7576 + Pony Canyon, Tohokushinsha Film Corporation, LIDEN FILMS + http://cdn.myanimelist.net/images/anime/12/49035.jpg + Aiura + + + 5 + 18413 + 7751 + Production I.G + http://cdn.myanimelist.net/images/anime/7/49979.jpg + Puchitto Gargantia + + + 5 + 18389 + + + http://cdn.myanimelist.net/images/anime/12/49219.jpg + Heisei Policemen!! + + + 1 + 16668 + 7487 + Sunrise, Aniplex, Aniplex of America + http://cdn.myanimelist.net/images/anime/3/49251.jpg + Kakumeiki Valvrave + + + 3 + 17263 + 7621 + Group TAC + http://cdn.myanimelist.net/images/anime/5/46099.jpg + Hanakappa Movie: Hana-sake! Pakkaan Chou no Kuni no Daibouken + + + 1 + 16049 + 7366 + J.C. Staff, FUNimation Entertainment, ASCII Media Works + http://cdn.myanimelist.net/images/anime/9/47547.jpg + Toaru Kagaku no Railgun S + + + 3 + 14669 + 7153 + Sotsu Agency, AIC A.S.T.A., Lantis, Pony Canyon, Movic, Delphi Sound, Marvelous AQL + http://cdn.myanimelist.net/images/anime/3/50315.jpg + Aura: Maryuuinkouga Saigo no Tatakai + + + 1 + 15225 + 7233 + J.C. Staff, Frontier Works, Media Factory, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/3/50105.jpg + Hentai Ouji to Warawanai Neko. + + + 3 + 12513 + 6755 + Xebec, AIC + http://cdn.myanimelist.net/images/anime/13/49181.jpg + Uchuu Senkan Yamato 2199 Movie 5: Boukyou no Gingakan Kuukan + + + 2 + 15847 + 7324 + Madhouse Studios, Marvel Entertainment + http://cdn.myanimelist.net/images/anime/6/43035.jpg + Iron Man: Rise of Technovore + + + 6 + 19287 + + Sony Music Entertainment + http://cdn.myanimelist.net/images/anime/3/51725.jpg + Reunion + + + 4 + 18419 + + Charaction + http://cdn.myanimelist.net/images/anime/9/49317.jpg + Boku no Imouto wa "Osaka Okan": Uchi no Onii-chan wa Tokyo Rule + + + 3 + 11577 + 6539 + Frontier Works, Media Factory, Movic, AT-X, White Fox, Kadokawa Pictures Japan, Mages + http://cdn.myanimelist.net/images/anime/3/50317.jpg + Steins;Gate: Fuka Ryouiki no Déjà vu + + + 3 + 14735 + 7159 + TMS Entertainment + http://cdn.myanimelist.net/images/anime/4/47419.jpg + Detective Conan Movie 17: Private Eye in the Distant Sea + + + 3 + 17113 + 7585 + TV Asahi, Asatsu DK, Shin-Ei Animation + http://cdn.myanimelist.net/images/anime/6/45745.jpg + Crayon Shin-chan Movie 21: Bakauma! B-Kyuu Gourmet Survival Battle!! + + + 2 + 17273 + 7626 + Studio Deen + http://cdn.myanimelist.net/images/anime/12/46525.jpg + Hetalia: The Beautiful World Specials + + + 4 + 17391 + 7732 + Ordet, LIDEN FILMS + http://cdn.myanimelist.net/images/anime/2/51475.jpg + Senyuu. Specials + + + 5 + 19337 + + CoMix Wave + http://cdn.myanimelist.net/images/anime/6/52087.jpg + Momoya x Peeping Life: Go en Desu yo! + + + 1 + 17873 + 7895 + TV Tokyo, Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/5/49285.jpg + Pokemon Best Wishes! Season 2: Decolora Adventure + + + 2 + 17137 + 7591 + + http://cdn.myanimelist.net/images/anime/10/45795.jpg + Saiyuuki Gaiden: Tokubetsu-hen - Kouga no Shou + + + 4 + 17635 + 7717 + P.A. Works + http://cdn.myanimelist.net/images/anime/10/50321.jpg + Koitabi: True Tours Nanto + + + 5 + 17637 + 7734 + Ordet, Encourage Films + http://cdn.myanimelist.net/images/anime/5/47451.jpg + Miyakawa-ke no Kuufuku + + + 4 + 18723 + + Digital Frontier + http://cdn.myanimelist.net/images/anime/11/50479.jpg + Soul Reviver + + + 1 + 18097 + 7765 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/2/49743.jpg + Inazuma Eleven Go: Galaxy + + + 1 + 18469 + + + http://cdn.myanimelist.net/images/anime/7/49447.jpg + Odoriko Clinoppe + + + 3 + 15771 + 7305 + Aniplex, A-1 Pictures, Kodansha, Toho Company + http://cdn.myanimelist.net/images/anime/7/48723.jpg + Saint☆Onii-san (Movie) + + + 4 + 16436 + 7428 + AIC Frontier, Flex Comics, G-mode + http://cdn.myanimelist.net/images/anime/9/44348.jpg + Tenshi no Drop + + + 3 + 20383 + + T.O Entertainment + http://cdn.myanimelist.net/images/anime/2/54141.jpg + Baka Mukashi Banashi Movie: Jijii Wars + + + 6 + 18799 + + NAZ + http://cdn.myanimelist.net/images/anime/6/50237.jpg + Take Your Way + + + 2 + 18835 + + + http://cdn.myanimelist.net/images/anime/11/50239.jpg + Hakuouki Reimeiroku Tokuten Disc + + + 4 + 17341 + 7766 + 8bit, Earth Star Entertainment + http://cdn.myanimelist.net/images/anime/6/46515.jpg + Yama no Susume: Kabette Kowakunai no? + + + 4 + 17351 + 7886 + Dogakobo, Earth Star Entertainment + http://cdn.myanimelist.net/images/anime/12/46513.jpg + Mangirl!: Asobu Henshuu Girl + + + 3 + 16149 + 7378 + Production I.G + http://cdn.myanimelist.net/images/anime/3/46509.jpg + Kick-Heart + + + 4 + 19029 + 7778 + Kinema Citrus + http://cdn.myanimelist.net/images/anime/6/51019.jpg + Yuyushiki Specials + + + 3 + 16782 + 7515 + CoMix Wave, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/11/50861.jpg + Kotonoha no Niwa + + + 3 + 19687 + + Vasoon Animation + http://cdn.myanimelist.net/images/anime/7/52701.jpg + Kuiba II + \ No newline at end of file diff --git a/data/db/season/2013_summer.xml b/data/db/season/2013_summer.xml index 02a5d8695..840f7b5f2 100644 --- a/data/db/season/2013_summer.xml +++ b/data/db/season/2013_summer.xml @@ -1,833 +1,951 @@ - - - - Summer 2013 - 1380423923 - - - 5 - 18953 - Studio 4°C - http://cdn.myanimelist.net/images/anime/3/50809.jpg - Hitotsubu ni Kawaranu Ai wo Komete - - - 4 - 18755 - Gathering - http://cdn.myanimelist.net/images/anime/9/50109.jpg - Donyatsu - - - 4 - 18989 - Production I.G - http://cdn.myanimelist.net/images/anime/6/51733.jpg - Ghost in the Shell: Arise - Another Mission - - - 3 - 16528 - FUNimation Entertainment, Wit Studio - http://cdn.myanimelist.net/images/anime/6/46549.jpg - Hal - - - 1 - 17389 - Studio Pierrot, NHK - http://cdn.myanimelist.net/images/anime/13/53589.jpg - Kingdom 2nd Season - - - 3 - 12477 - Asmik Ace Entertainment, Purple Cow Studio Japan - http://cdn.myanimelist.net/images/anime/12/52415.jpg - Sakasama no Patema - - - 1 - 18983 - Shirogumi - http://cdn.myanimelist.net/images/anime/3/51247.jpg - Yuuto-kun ga Iku - - - 3 - 12515 - Xebec, AIC - http://cdn.myanimelist.net/images/anime/3/51259.jpg - Uchuu Senkan Yamato 2199 Movie 6: Toutatsu! Dai Magellan - - - 2 - 17725 - Manglobe - http://cdn.myanimelist.net/images/anime/2/47437.jpg - Magical☆Star Kanon 100% - - - 5 - 19133 - CoMix Wave, Baramiri - http://cdn.myanimelist.net/images/anime/10/52865.jpg - Turning Girls - - - 4 - 16934 - Kyoto Animation, Lantis, Pony Canyon, TBS, Rakuonsha - http://cdn.myanimelist.net/images/anime/7/45512.jpg - Chuunibyou demo Koi ga Shitai!: Kirameki no... Slapstick Noel - - - 2 - 16762 - Asread - http://cdn.myanimelist.net/images/anime/3/53247.jpg - Mirai Nikki Redial - - - 4 - 19845 - Aniplex, A-1 Pictures - http://cdn.myanimelist.net/images/anime/7/54407.jpg - Ore no Imouto ga Konnani Kawaii Wake ga Nai. Short Anime Specials - - - 2 - 18039 - Hoods Entertainment - http://cdn.myanimelist.net/images/anime/12/52033.jpg - Rescue Me! - - - 2 - 15807 - - http://cdn.myanimelist.net/images/anime/10/43771.jpg - Ro-Kyu-Bu!: Tomoka no Ichigo Sundae - - - 6 - 19185 - - http://cdn.myanimelist.net/images/anime/9/51847.jpg - HORIZON feat. Hatsune Miku - - - 4 - 19447 - Satelight, Lantis - http://cdn.myanimelist.net/images/anime/4/52145.jpg - Arata Kangatari Picture Drama - - - 3 - 17187 - Production I.G, Bandai Visual, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/8/46673.jpg - Ghost in the Shell: Arise - Border:1 Ghost Pain - - - 3 - 18817 - Digital Media Lab - http://cdn.myanimelist.net/images/anime/6/50207.jpg - Yasai no Yousei: Quiz Gekijou - - - 3 - 18109 - - http://cdn.myanimelist.net/images/anime/2/48073.jpg - Glass no Kamen Desu ga the Movie: Onna Spy no Koi! Murasaki no Bara wa Kiken na Kaori!? - - - 1 - 14829 - Silver Link, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/7/52603.jpg - Fate/kaleid liner Prisma☆Illya - - - 4 - 18849 - - http://cdn.myanimelist.net/images/anime/5/51431.jpg - Natsume Yuujinchou LaLa Special - - - 2 - 17875 - Feel - http://cdn.myanimelist.net/images/anime/6/47627.jpg - Papa no Iukoto wo Kikinasai! OVA - - - 4 - 19255 - Arms, Genco, Lantis - http://cdn.myanimelist.net/images/anime/13/52085.jpg - Hyakka Ryouran: Samurai Bride Specials - - - 2 - 16614 - 8bit - http://cdn.myanimelist.net/images/anime/11/44772.jpg - Busou Shinki OVA - - - 4 - 19431 - LMD, Cammot - http://cdn.myanimelist.net/images/anime/4/52095.jpg - Gokujo.: Souda Onsen ni Ikou!! - - - 4 - 19297 - Earth Star Entertainment - http://cdn.myanimelist.net/images/anime/7/51749.jpg - Ketsuekigata-kun! Special - - - 3 - 10687 - Toei Animation - http://cdn.myanimelist.net/images/anime/9/36301.jpg - Saint Seiya (Movie) - - - 2 - 19171 - Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/12/52537.jpg - Sanjougattai Transformers Go! - - - 1 - 17831 - Gonzo, Avex Entertainment, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/13/52605.jpg - Inu to Hasami wa Tsukaiyou - - - 5 - 19169 - DLE - http://cdn.myanimelist.net/images/anime/13/51435.jpg - Go! Go! Kadendanshi - - - 2 - 17395 - Hoods Entertainment - http://cdn.myanimelist.net/images/anime/9/49013.jpg - Ark IX - - - 1 - 15605 - FUNimation Entertainment, Brains Base - http://cdn.myanimelist.net/images/anime/5/51409.jpg - Brothers Conflict - - - 1 - 18771 - Studio Deen - http://cdn.myanimelist.net/images/anime/5/51427.jpg - Gifuu Doudou!!: Kanetsugu to Keiji - - - 1 - 18523 - Ordet, LIDEN FILMS - http://cdn.myanimelist.net/images/anime/5/51879.jpg - Senyuu. 2 - - - 1 - 15731 - TYO Animations - http://cdn.myanimelist.net/images/anime/3/53895.jpg - Tamayura: More Aggressive - - - 1 - 18507 - Kyoto Animation, Lantis, Animation Do, Iwatobi High School Swimming Club - http://cdn.myanimelist.net/images/anime/6/51107.jpg - Free! - - - 1 - 18495 - Nomad - http://cdn.myanimelist.net/images/anime/12/52609.jpg - Kitakubu Katsudou Kiroku - - - 6 - 19519 - - http://cdn.myanimelist.net/images/anime/3/52411.jpg - Dream Creator feat. GUMI - - - 2 - 15963 - - http://cdn.myanimelist.net/images/anime/3/43525.jpg - Seitokai no Shukujitsu - - - 1 - 18041 - Studio Deen, TBS, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/10/52079.jpg - Rozen Maiden (2013) - - - 1 - 17821 - Gainax, Pony Canyon, TBS, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/13/52125.jpg - Stella Jogakuin Koutou-ka C³-bu - - - 1 - 15793 - Satelight, Starchild Records - http://cdn.myanimelist.net/images/anime/9/47981.jpg - Senki Zesshou Symphogear G: In the Distance, That Day, When the Star Became Music... - - - 1 - 16051 - Warner Bros., Project No.9, The Klock Worx, Studio Blanc, ASCII Media Works - http://cdn.myanimelist.net/images/anime/8/50697.jpg - Ro-Kyu-Bu! SS - - - 1 - 18119 - Aniplex, A-1 Pictures, Half H.P Studio, Aniplex of America - http://cdn.myanimelist.net/images/anime/13/51579.jpg - Servant x Service - - - 1 - 16592 - Geneon Universal Entertainment, FUNimation Entertainment, Lerche, Showgate - http://cdn.myanimelist.net/images/anime/4/51463.jpg - Danganronpa: Kibou no Gakuen to Zetsubou no Koukousei - The Animation - - - 1 - 16353 - Aniplex, Dentsu, Dogakobo - http://cdn.myanimelist.net/images/anime/12/50257.jpg - Love Lab - - - 1 - 19207 - AIC Frontier - http://cdn.myanimelist.net/images/anime/7/51505.jpg - Maji de Otaku na English! Ribbon-chan: Eigo de Tatakau Mahou Shoujo - The TV - - - 1 - 16732 - Media Factory, Sentai Filmworks, Studio Gokumi, Showgate - http://cdn.myanimelist.net/images/anime/8/51379.jpg - Kiniro Mosaic - - - 3 - 18355 - - http://cdn.myanimelist.net/images/anime/2/49477.jpg - Minna de Teasobi: Anpanman to Itazura Obake - - - 3 - 18357 - TMS Entertainment - http://cdn.myanimelist.net/images/anime/9/49475.jpg - Sore Ike! Anpanman: Tobase! Kibou no Handkerchief - - - 3 - 15335 - Sunrise, TV Tokyo, Aniplex, Dentsu, Bandai - http://cdn.myanimelist.net/images/anime/12/49197.jpg - Gintama: Kanketsu-hen - Yorozuya yo Eien Nare - - - 1 - 17651 - Aniplex, AIC, Aniplex of America - http://cdn.myanimelist.net/images/anime/4/51071.jpg - Genei wo Kakeru Taiyou - - - 1 - 15883 - Hoods Entertainment, Sentai Filmworks, Amber Film Works - http://cdn.myanimelist.net/images/anime/7/52529.jpg - Fantasista Doll - - - 1 - 18465 - Production I.G, Starchild Records, NIS America, Inc. - http://cdn.myanimelist.net/images/anime/11/52935.jpg - Genshiken Nidaime - - - 4 - 19285 - Production I.G, Mainichi Broadcasting, Wit Studio, Shingeki no Kyojin Team - http://cdn.myanimelist.net/images/anime/3/51715.jpg - Shingeki no Kyojin Recap - - - 1 - 15451 - FUNimation Entertainment, TNK - http://cdn.myanimelist.net/images/anime/12/47729.jpg - High School DxD New - - - 1 - 18055 - Bandai Visual, Studio Deen, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/10/50885.jpg - Hakkenden: Touhou Hakken Ibun 2nd Season - - - 1 - 16009 - Madhouse Studios, Starchild Records, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/2/52127.jpg - Kamisama no Inai Nichiyoubi - - - 1 - 17074 - Aniplex, Shaft, Kodansha, Aniplex of America - http://cdn.myanimelist.net/images/anime/3/52133.jpg - Monogatari Series: Second Season - - - 4 - 19397 - Gonzo, TV Tokyo - http://cdn.myanimelist.net/images/anime/11/52413.jpg - Zettai Bouei Leviathan: Mini Takibi Gekijou - - - 1 - 18121 - MAPPA, Earth Star Entertainment - http://cdn.myanimelist.net/images/anime/8/48821.jpg - Teekyuu 2 - - - 1 - 17909 - Bandai Visual, Lantis, P.A. Works, YTV, Magic Capsule, Uchouten Kazoku Production Committee - http://cdn.myanimelist.net/images/anime/4/50889.jpg - Uchouten Kazoku - - - 1 - 11633 - Brains Base, Viz Media - http://cdn.myanimelist.net/images/anime/11/47677.jpg - Blood Lad - - - 1 - 18099 - Takeshobo, Seven - http://cdn.myanimelist.net/images/anime/6/50651.jpg - Recorder to Randoseru Mi☆ - - - 1 - 16890 - TV Tokyo, Dogakobo - http://cdn.myanimelist.net/images/anime/12/49573.jpg - Makai Ouji: Devils and Realist - - - 1 - 16706 - TV Tokyo, Manglobe - http://cdn.myanimelist.net/images/anime/6/51949.jpg - Kami nomi zo Shiru Sekai: Megami-hen - - - 1 - 16742 - Square Enix, Silver Link, Sentai Filmworks, Watamote Production Committee - http://cdn.myanimelist.net/images/anime/12/51619.jpg - Watashi ga Motenai no wa Dou Kangaetemo Omaera ga Warui! - - - 1 - 19305 - Yomiuri Telecasting Corporation, DLE - http://cdn.myanimelist.net/images/anime/12/51787.jpg - Nyuru Nyuru!! Kakusen-kun - - - 1 - 19099 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/3/51207.jpg - Mewtwo: Kakusei e no Prologue - - - 1 - 16918 - Aniplex, A-1 Pictures, Fuji TV, Aniplex of America - http://cdn.myanimelist.net/images/anime/6/49237.jpg - Gin no Saji - - - 1 - 16157 - Frontier Works, FUNimation Entertainment, Media Factory, Idea Factory, David Production, Magic Capsule - http://cdn.myanimelist.net/images/anime/6/52141.jpg - Choujigen Game Neptune: The Animation - - - 4 - 16678 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/9/44900.jpg - Pokemon: Pikachu to Eevee Friends - - - 3 - 16680 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/9/47615.jpg - Pokemon Best Wishes! Season 2: Shinsoku no Genosect - Mewtwo Kakusei - - - 1 - 18229 - VAP, Tatsunoko Productions, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/7/52471.jpg - Gatchaman Crowds - - - 1 - 17741 - Gonzo, TV Tokyo - http://cdn.myanimelist.net/images/anime/13/50685.jpg - Kimi no Iru Machi - - - 1 - 17267 - J.C. Staff, Nomad, Bushiroad Inc. - http://cdn.myanimelist.net/images/anime/11/51931.jpg - Futari wa Milky Holmes - - - 3 - 19165 - ufotable - http://cdn.myanimelist.net/images/anime/7/51943.jpg - Fate/Zero Cafe - - - 5 - 19495 - - http://cdn.myanimelist.net/images/anime/5/53423.jpg - Hakusai Anime - - - 1 - 19383 - TV Tokyo - http://cdn.myanimelist.net/images/anime/11/51995.jpg - Yami Shibai - - - 6 - 19521 - - http://cdn.myanimelist.net/images/anime/2/52409.jpg - GAME OVER feat. Hatsune Miku - - - 4 - 19391 - Production I.G - http://cdn.myanimelist.net/images/anime/8/53477.jpg - Shingeki no Kyojin Picture Drama - - - 5 - 19581 - Studio Hibari, Kodansha - http://cdn.myanimelist.net/images/anime/2/52443.jpg - Houseki no Kuni - - - 3 - 17677 - - http://cdn.myanimelist.net/images/anime/4/53169.jpg - Buki yo Saraba - - - 3 - 17679 - - http://cdn.myanimelist.net/images/anime/10/53167.jpg - Gambo - - - 3 - 16662 - Studio Ghibli - http://cdn.myanimelist.net/images/anime/8/52353.jpg - Kaze Tachinu - - - 2 - 16444 - Diomedea - http://cdn.myanimelist.net/images/anime/6/52575.jpg - Mondaiji-tachi ga Isekai kara Kuru Sou Desu yo? OVA - - - 4 - 19573 - Sunrise - http://cdn.myanimelist.net/images/anime/6/53159.jpg - Short Peace Opening - - - 4 - 19619 - TV Tokyo, Studio Comet, Studio Jack - http://cdn.myanimelist.net/images/anime/11/52823.jpg - Jewelpet Tinkle Special - - - 2 - 15037 - Asread, Mages - http://cdn.myanimelist.net/images/anime/3/54209.jpg - Corpse Party: Tortured Souls - Bougyakusareta Tamashii no Jukyou - - - 2 - 16868 - Zexcs - http://cdn.myanimelist.net/images/anime/13/53861.jpg - Sukitte Ii na yo.: Dareka ga - - - 4 - 19697 - J.C. Staff - http://cdn.myanimelist.net/images/anime/2/52717.jpg - Toaru Kagaku no Railgun S Specials - - - 2 - 19575 - - http://cdn.myanimelist.net/images/anime/10/52439.jpg - Kobayashi ga Kawai Sugite Tsurai!! - - - 2 - 17655 - Sunrise, Mainichi Broadcasting - http://cdn.myanimelist.net/images/anime/5/52775.jpg - Mobile Suit Gundam AGE: Memory of Eden - - - 3 - 17699 - Toei Animation - http://cdn.myanimelist.net/images/anime/6/47417.jpg - Toriko Movie: Bishokushin no Special Menu - - - 4 - 19811 - Studio Deen - http://cdn.myanimelist.net/images/anime/9/52837.jpg - Waanabi.jk - - - 2 - 19351 - - http://cdn.myanimelist.net/images/anime/5/51883.jpg - Nijiiro☆Prism Girl - - - 5 - 18919 - Gonzo - http://cdn.myanimelist.net/images/anime/11/53019.jpg - The Midnight★Animal - - - 5 - 19469 - Opera House - http://cdn.myanimelist.net/images/anime/10/54195.jpg - Saiki Kusuo no Ψ Nan - - - 5 - 19925 - CoMix Wave - http://cdn.myanimelist.net/images/anime/5/53135.jpg - KY Kei JC Kuukichan - - - 2 - 20221 - Feel, Starchild Records - http://cdn.myanimelist.net/images/anime/11/54213.jpg - Minami-ke Natsuyasumi - - - 2 - 18177 - - http://cdn.myanimelist.net/images/anime/13/53213.jpg - Yowamushi Pedal: Special Ride - - - 2 - 16866 - Brains Base - http://cdn.myanimelist.net/images/anime/8/53925.jpg - Tonari no Kaibutsu-kun: Tonari no Gokudou-kun - - - 2 - 16700 - Studio Deen - http://cdn.myanimelist.net/images/anime/12/51063.jpg - Higurashi no Naku Koro ni Kaku: Outbreak - - - 4 - 20155 - Project No.9 - http://cdn.myanimelist.net/images/anime/8/53685.jpg - Ro-Kyu-Bu! SS Recap - - - 2 - 17855 - Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/10/47599.jpg - Namiuchigiwa no Muromi-san OVA - - - 2 - 18393 - Satelight, A-1 Pictures - http://cdn.myanimelist.net/images/anime/9/53703.jpg - Fairy Tail x Rave - - - 4 - 18857 - Aniplex, A-1 Pictures, Aniplex of America - http://cdn.myanimelist.net/images/anime/9/51167.jpg - Ore no Imouto ga Konnani Kawaii Wake ga Nai. Specials - - - 2 - 18661 - TMS Entertainment - http://cdn.myanimelist.net/images/anime/7/50829.jpg - Kamisama Hajimemashita OVA - - - 3 - 13117 - Geneon Universal Entertainment, Studio Deen, Frontier Works, The Klock Worx - http://cdn.myanimelist.net/images/anime/9/49399.jpg - Hakuouki Movie 1: Kyoto Ranbu - - - 4 - 19123 - Toei Animation, Fuji TV - http://cdn.myanimelist.net/images/anime/12/51777.jpg - One Piece: Episode of Merry - Mou Hitori no Nakama no Monogatari - - - 3 - 12517 - Xebec, AIC - http://cdn.myanimelist.net/images/anime/11/35109.jpg - Uchuu Senkan Yamato 2199 Movie 7: Soshite Kan wa Iku - - - 5 - 20355 - Sunrise - http://cdn.myanimelist.net/images/anime/13/54101.jpg - Zeonic Toyota Special Movie - - - 5 - 20359 - Kodansha - http://cdn.myanimelist.net/images/anime/3/54103.jpg - Yamada-kun to 7-nin no Majo - - - 4 - 19211 - Production I.G, Viz Media - http://cdn.myanimelist.net/images/anime/8/51509.jpg - Suisei no Gargantia Specials - - - 4 - 20035 - J.C. Staff, Geneon Universal Entertainment - http://cdn.myanimelist.net/images/anime/9/53541.jpg - Toaru Majutsu no Index: Endymion no Kiseki Special - - - 2 - 17643 - J.C. Staff - http://cdn.myanimelist.net/images/anime/10/47535.jpg - Little Busters! OVA - - - 3 - 15039 - Aniplex, A-1 Pictures - http://cdn.myanimelist.net/images/anime/3/53903.jpg - Ano Hi Mita Hana no Namae wo Bokutachi wa Mada Shiranai. Movie - + + + + Summer 2013 + 1380423923 + + + 5 + 18953 + 8236 + Studio 4°C + http://cdn.myanimelist.net/images/anime/3/50809.jpg + Hitotsubu ni Kawaranu Ai wo Komete + + + 4 + 18755 + 8051 + Gathering + http://cdn.myanimelist.net/images/anime/9/50109.jpg + Donyatsu + + + 4 + 18989 + 8233 + Production I.G + http://cdn.myanimelist.net/images/anime/6/51733.jpg + Ghost in the Shell: Arise - Another Mission + + + 3 + 16528 + 7451 + FUNimation Entertainment, Wit Studio + http://cdn.myanimelist.net/images/anime/6/46549.jpg + Hal + + + 1 + 17389 + 7680 + Studio Pierrot, NHK + http://cdn.myanimelist.net/images/anime/13/53589.jpg + Kingdom 2nd Season + + + 3 + 12477 + 6742 + Asmik Ace Entertainment, Purple Cow Studio Japan + http://cdn.myanimelist.net/images/anime/12/52415.jpg + Sakasama no Patema + + + 1 + 18983 + + Shirogumi + http://cdn.myanimelist.net/images/anime/3/51247.jpg + Yuuto-kun ga Iku + + + 3 + 12515 + 6756 + Xebec, AIC + http://cdn.myanimelist.net/images/anime/3/51259.jpg + Uchuu Senkan Yamato 2199 Movie 6: Toutatsu! Dai Magellan + + + 2 + 17725 + 7688 + Manglobe + http://cdn.myanimelist.net/images/anime/2/47437.jpg + Magical☆Star Kanon 100% + + + 5 + 19133 + 7804 + CoMix Wave, Baramiri + http://cdn.myanimelist.net/images/anime/10/52865.jpg + Turning Girls + + + 4 + 16934 + 7556 + Kyoto Animation, Lantis, Pony Canyon, TBS, Rakuonsha + http://cdn.myanimelist.net/images/anime/7/45512.jpg + Chuunibyou demo Koi ga Shitai!: Kirameki no... Slapstick Noel + + + 2 + 16762 + 7510 + Asread + http://cdn.myanimelist.net/images/anime/3/53247.jpg + Mirai Nikki Redial + + + 4 + 19845 + + Aniplex, A-1 Pictures + http://cdn.myanimelist.net/images/anime/7/54407.jpg + Ore no Imouto ga Konnani Kawaii Wake ga Nai. Short Anime Specials + + + 2 + 18039 + 7691 + Hoods Entertainment + http://cdn.myanimelist.net/images/anime/12/52033.jpg + Rescue Me! + + + 2 + 15807 + 7313 + + http://cdn.myanimelist.net/images/anime/10/43771.jpg + Ro-Kyu-Bu!: Tomoka no Ichigo Sundae + + + 6 + 19185 + + + http://cdn.myanimelist.net/images/anime/9/51847.jpg + HORIZON feat. Hatsune Miku + + + 4 + 19447 + + Satelight, Lantis + http://cdn.myanimelist.net/images/anime/4/52145.jpg + Arata Kangatari Picture Drama + + + 3 + 17187 + 7602 + Production I.G, Bandai Visual, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/8/46673.jpg + Ghost in the Shell: Arise - Border:1 Ghost Pain + + + 3 + 18817 + + Digital Media Lab + http://cdn.myanimelist.net/images/anime/6/50207.jpg + Yasai no Yousei: Quiz Gekijou + + + 3 + 18109 + 7683 + + http://cdn.myanimelist.net/images/anime/2/48073.jpg + Glass no Kamen Desu ga the Movie: Onna Spy no Koi! Murasaki no Bara wa Kiken na Kaori!? + + + 1 + 14829 + 7173 + Silver Link, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/7/52603.jpg + Fate/kaleid liner Prisma☆Illya + + + 4 + 18849 + 7747 + Brains Base + http://cdn.myanimelist.net/images/anime/5/51431.jpg + Natsume Yuujinchou LaLa Special + + + 2 + 17875 + 7685 + Feel + http://cdn.myanimelist.net/images/anime/6/47627.jpg + Papa no Iukoto wo Kikinasai! OVA + + + 4 + 19255 + 7917 + Arms, Genco, Lantis + http://cdn.myanimelist.net/images/anime/13/52085.jpg + Hyakka Ryouran: Samurai Bride Specials + + + 2 + 16614 + 7475 + 8bit + http://cdn.myanimelist.net/images/anime/11/44772.jpg + Busou Shinki OVA + + + 4 + 19431 + + LMD, Cammot + http://cdn.myanimelist.net/images/anime/4/52095.jpg + Gokujo.: Souda Onsen ni Ikou!! + + + 4 + 19297 + 8240 + Earth Star Entertainment + http://cdn.myanimelist.net/images/anime/7/51749.jpg + Ketsuekigata-kun! Special + + + 3 + 10687 + 6293 + Toei Animation + http://cdn.myanimelist.net/images/anime/9/36301.jpg + Saint Seiya (Movie) + + + 2 + 19171 + + Tatsunoko Productions + http://cdn.myanimelist.net/images/anime/12/52537.jpg + Sanjougattai Transformers Go! + + + 1 + 17831 + 7673 + Gonzo, Avex Entertainment, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/13/56313.jpg + Inu to Hasami wa Tsukaiyou + + + 5 + 19169 + + DLE + http://cdn.myanimelist.net/images/anime/13/51435.jpg + Go! Go! Kadendanshi + + + 2 + 17395 + 7686 + Hoods Entertainment + http://cdn.myanimelist.net/images/anime/9/49013.jpg + Ark IX + + + 1 + 15605 + 7280 + FUNimation Entertainment, Brains Base + http://cdn.myanimelist.net/images/anime/5/51409.jpg + Brothers Conflict + + + 1 + 18771 + 7744 + Studio Deen + http://cdn.myanimelist.net/images/anime/5/51427.jpg + Gifuu Doudou!!: Kanetsugu to Keiji + + + 1 + 18523 + 7719 + Ordet, LIDEN FILMS + http://cdn.myanimelist.net/images/anime/5/51879.jpg + Senyuu. 2 + + + 1 + 15731 + 7299 + TYO Animations + http://cdn.myanimelist.net/images/anime/3/53895.jpg + Tamayura: More Aggressive + + + 1 + 18507 + 7664 + Kyoto Animation, Lantis, Animation Do, Iwatobi High School Swimming Club + http://cdn.myanimelist.net/images/anime/6/51107.jpg + Free! + + + 1 + 18495 + 7676 + Nomad + http://cdn.myanimelist.net/images/anime/12/52609.jpg + Kitakubu Katsudou Kiroku + + + 6 + 19519 + + + http://cdn.myanimelist.net/images/anime/3/52411.jpg + Dream Creator feat. GUMI + + + 2 + 15963 + 7349 + AIC + http://cdn.myanimelist.net/images/anime/3/43525.jpg + Seitokai no Shukujitsu + + + 1 + 18041 + 7678 + Studio Deen, TBS, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/10/52079.jpg + Rozen Maiden (2013) + + + 1 + 17821 + 7671 + Gainax, Pony Canyon, TBS, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/13/52125.jpg + Stella Jogakuin Koutou-ka C³-bu + + + 1 + 15793 + 7311 + Satelight, Starchild Records + http://cdn.myanimelist.net/images/anime/9/47981.jpg + Senki Zesshou Symphogear G: In the Distance, That Day, When the Star Became Music... + + + 1 + 16051 + 7367 + Warner Bros., Project No.9, The Klock Worx, Studio Blanc, ASCII Media Works + http://cdn.myanimelist.net/images/anime/8/50697.jpg + Ro-Kyu-Bu! SS + + + 1 + 18119 + 7669 + Aniplex, A-1 Pictures, Half H.P Studio, Aniplex of America + http://cdn.myanimelist.net/images/anime/13/51579.jpg + Servant x Service + + + 1 + 16592 + 7469 + Geneon Universal Entertainment, FUNimation Entertainment, Lerche, Showgate + http://cdn.myanimelist.net/images/anime/4/51463.jpg + Danganronpa: Kibou no Gakuen to Zetsubou no Koukousei - The Animation + + + 1 + 16353 + 7410 + Aniplex, Dentsu, Dogakobo + http://cdn.myanimelist.net/images/anime/12/50257.jpg + Love Lab + + + 1 + 19207 + 7834 + AIC Frontier + http://cdn.myanimelist.net/images/anime/7/51505.jpg + Maji de Otaku na English! Ribbon-chan: Eigo de Tatakau Mahou Shoujo - The TV + + + 1 + 16732 + 7501 + Media Factory, Sentai Filmworks, Studio Gokumi, Showgate + http://cdn.myanimelist.net/images/anime/8/51379.jpg + Kiniro Mosaic + + + 3 + 18355 + + + http://cdn.myanimelist.net/images/anime/2/49477.jpg + Minna de Teasobi: Anpanman to Itazura Obake + + + 3 + 18357 + + TMS Entertainment + http://cdn.myanimelist.net/images/anime/9/49475.jpg + Sore Ike! Anpanman: Tobase! Kibou no Handkerchief + + + 3 + 15335 + 7241 + Sunrise, TV Tokyo, Aniplex, Dentsu, Bandai + http://cdn.myanimelist.net/images/anime/12/49197.jpg + Gintama: Kanketsu-hen - Yorozuya yo Eien Nare + + + 1 + 17651 + 7670 + Aniplex, AIC, Aniplex of America + http://cdn.myanimelist.net/images/anime/4/51071.jpg + Genei wo Kakeru Taiyou + + + 1 + 15883 + 7332 + Hoods Entertainment, Sentai Filmworks, Amber Film Works + http://cdn.myanimelist.net/images/anime/7/52529.jpg + Fantasista Doll + + + 1 + 18465 + 7677 + Production I.G, Starchild Records, NIS America, Inc. + http://cdn.myanimelist.net/images/anime/11/52935.jpg + Genshiken Nidaime + + + 4 + 19285 + 7838 + Production I.G, Mainichi Broadcasting, Wit Studio, Shingeki no Kyojin Team + http://cdn.myanimelist.net/images/anime/3/51715.jpg + Shingeki no Kyojin Recap + + + 1 + 15451 + 7258 + FUNimation Entertainment, TNK + http://cdn.myanimelist.net/images/anime/12/47729.jpg + High School DxD New + + + 1 + 18055 + 7679 + Bandai Visual, Studio Deen, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/10/50885.jpg + Hakkenden: Touhou Hakken Ibun 2nd Season + + + 1 + 16009 + 7358 + Madhouse Studios, Starchild Records, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/2/52127.jpg + Kamisama no Inai Nichiyoubi + + + 1 + 17074 + 7573 + Aniplex, Shaft, Kodansha, Aniplex of America + http://cdn.myanimelist.net/images/anime/3/52133.jpg + Monogatari Series: Second Season + + + 4 + 19397 + + Gonzo, TV Tokyo + http://cdn.myanimelist.net/images/anime/11/52413.jpg + Zettai Bouei Leviathan: Mini Takibi Gekijou + + + 1 + 18121 + 7718 + MAPPA, Earth Star Entertainment + http://cdn.myanimelist.net/images/anime/8/48821.jpg + Teekyuu 2 + + + 1 + 17909 + 7675 + Bandai Visual, Lantis, P.A. Works, YTV, Magic Capsule, Uchouten Kazoku Production Committee + http://cdn.myanimelist.net/images/anime/4/50889.jpg + Uchouten Kazoku + + + 1 + 11633 + 6552 + Brains Base, Viz Media + http://cdn.myanimelist.net/images/anime/11/47677.jpg + Blood Lad + + + 1 + 18099 + 7695 + Takeshobo, Seven + http://cdn.myanimelist.net/images/anime/6/50651.jpg + Recorder to Randoseru Mi☆ + + + 1 + 16890 + 7544 + TV Tokyo, Dogakobo + http://cdn.myanimelist.net/images/anime/12/49573.jpg + Makai Ouji: Devils and Realist + + + 1 + 16706 + 7496 + TV Tokyo, Manglobe + http://cdn.myanimelist.net/images/anime/6/51949.jpg + Kami nomi zo Shiru Sekai: Megami-hen + + + 1 + 16742 + 7504 + Square Enix, Silver Link, Sentai Filmworks, Watamote Production Committee + http://cdn.myanimelist.net/images/anime/12/51619.jpg + Watashi ga Motenai no wa Dou Kangaetemo Omaera ga Warui! + + + 1 + 19305 + 7830 + Yomiuri Telecasting Corporation, DLE + http://cdn.myanimelist.net/images/anime/12/51787.jpg + Nyuru Nyuru!! Kakusen-kun + + + 1 + 19099 + 7789 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/3/51207.jpg + Mewtwo: Kakusei e no Prologue + + + 1 + 16918 + 7553 + Aniplex, A-1 Pictures, Fuji TV, Aniplex of America + http://cdn.myanimelist.net/images/anime/6/49237.jpg + Gin no Saji + + + 1 + 16157 + 7379 + Frontier Works, FUNimation Entertainment, Media Factory, Idea Factory, David Production, Magic Capsule + http://cdn.myanimelist.net/images/anime/6/52141.jpg + Choujigen Game Neptune: The Animation + + + 4 + 16678 + 7488 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/9/44900.jpg + Pokemon: Pikachu to Eevee Friends + + + 3 + 16680 + 7489 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/9/47615.jpg + Pokemon Best Wishes! Season 2: Shinsoku no Genosect - Mewtwo Kakusei + + + 1 + 18229 + 7681 + VAP, Tatsunoko Productions, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/7/52471.jpg + Gatchaman Crowds + + + 1 + 17741 + 7672 + Gonzo, TV Tokyo + http://cdn.myanimelist.net/images/anime/13/50685.jpg + Kimi no Iru Machi + + + 1 + 17267 + 7623 + J.C. Staff, Nomad, Bushiroad Inc. + http://cdn.myanimelist.net/images/anime/11/51931.jpg + Futari wa Milky Holmes + + + 3 + 19165 + 7873 + ufotable + http://cdn.myanimelist.net/images/anime/7/51943.jpg + Fate/Zero Cafe + + + 5 + 19495 + + + http://cdn.myanimelist.net/images/anime/5/53423.jpg + Hakusai Anime + + + 1 + 19383 + 7840 + TV Tokyo + http://cdn.myanimelist.net/images/anime/11/51995.jpg + Yami Shibai + + + 6 + 19521 + + + http://cdn.myanimelist.net/images/anime/2/52409.jpg + GAME OVER feat. Hatsune Miku + + + 4 + 19391 + 7889 + Production I.G + http://cdn.myanimelist.net/images/anime/8/53477.jpg + Shingeki no Kyojin Picture Drama + + + 5 + 19581 + + Studio Hibari, Kodansha + http://cdn.myanimelist.net/images/anime/2/52443.jpg + Houseki no Kuni + + + 3 + 17677 + 7682 + Sentai Filmworks + http://cdn.myanimelist.net/images/anime/4/53169.jpg + Buki yo Saraba + + + 3 + 17679 + 8280 + Sentai Filmworks + http://cdn.myanimelist.net/images/anime/10/53167.jpg + Gambo + + + 3 + 16662 + 7485 + Studio Ghibli + http://cdn.myanimelist.net/images/anime/8/52353.jpg + Kaze Tachinu + + + 2 + 16444 + 7430 + Diomedea + http://cdn.myanimelist.net/images/anime/6/52575.jpg + Mondaiji-tachi ga Isekai kara Kuru Sou Desu yo? OVA + + + 4 + 19573 + 8215 + Sunrise + http://cdn.myanimelist.net/images/anime/6/53159.jpg + Short Peace Opening + + + 4 + 19619 + + TV Tokyo, Studio Comet, Studio Jack + http://cdn.myanimelist.net/images/anime/11/52823.jpg + Jewelpet Tinkle Special + + + 2 + 15037 + 7199 + Asread, Mages + http://cdn.myanimelist.net/images/anime/3/54209.jpg + Corpse Party: Tortured Souls - Bougyakusareta Tamashii no Jukyou + + + 2 + 16868 + 7542 + Zexcs + http://cdn.myanimelist.net/images/anime/13/53861.jpg + Sukitte Ii na yo.: Dareka ga + + + 4 + 19697 + 7969 + J.C. Staff + http://cdn.myanimelist.net/images/anime/2/52717.jpg + Toaru Kagaku no Railgun S Specials + + + 2 + 19575 + 8119 + + http://cdn.myanimelist.net/images/anime/10/52439.jpg + Kobayashi ga Kawai Sugite Tsurai!! + + + 2 + 17655 + 7689 + Sunrise, Mainichi Broadcasting + http://cdn.myanimelist.net/images/anime/5/52775.jpg + Mobile Suit Gundam AGE: Memory of Eden + + + 3 + 17699 + 7684 + Toei Animation + http://cdn.myanimelist.net/images/anime/6/47417.jpg + Toriko Movie: Bishokushin no Special Menu + + + 4 + 19811 + 8238 + Studio Deen + http://cdn.myanimelist.net/images/anime/9/52837.jpg + Waanabi.jk + + + 2 + 19351 + 7859 + + http://cdn.myanimelist.net/images/anime/5/51883.jpg + Nijiiro☆Prism Girl + + + 5 + 18919 + 7905 + Gonzo + http://cdn.myanimelist.net/images/anime/11/53019.jpg + The Midnight★Animal + + + 5 + 19469 + + Opera House + http://cdn.myanimelist.net/images/anime/10/54195.jpg + Saiki Kusuo no Ψ Nan + + + 5 + 19925 + 7962 + CoMix Wave + http://cdn.myanimelist.net/images/anime/5/53135.jpg + KY Kei JC Kuukichan + + + 2 + 20221 + 7968 + Feel, Starchild Records + http://cdn.myanimelist.net/images/anime/11/54213.jpg + Minami-ke Natsuyasumi + + + 2 + 18177 + 7693 + TMS Entertainment + http://cdn.myanimelist.net/images/anime/13/53213.jpg + Yowamushi Pedal: Special Ride + + + 2 + 16866 + 7541 + Brains Base + http://cdn.myanimelist.net/images/anime/8/53925.jpg + Tonari no Kaibutsu-kun: Tonari no Gokudou-kun + + + 2 + 16700 + 7494 + Studio Deen + http://cdn.myanimelist.net/images/anime/12/51063.jpg + Higurashi no Naku Koro ni Kaku: Outbreak + + + 4 + 20155 + + Project No.9 + http://cdn.myanimelist.net/images/anime/8/53685.jpg + Ro-Kyu-Bu! SS Recap + + + 2 + 17855 + 7690 + Tatsunoko Productions + http://cdn.myanimelist.net/images/anime/10/47599.jpg + Namiuchigiwa no Muromi-san OVA + + + 2 + 18393 + 7692 + Satelight, A-1 Pictures + http://cdn.myanimelist.net/images/anime/9/53703.jpg + Fairy Tail x Rave + + + 4 + 18857 + 7822 + Aniplex, A-1 Pictures, Aniplex of America + http://cdn.myanimelist.net/images/anime/9/51167.jpg + Ore no Imouto ga Konnani Kawaii Wake ga Nai. Specials + + + 2 + 18661 + 7901 + TMS Entertainment + http://cdn.myanimelist.net/images/anime/7/50829.jpg + Kamisama Hajimemashita OVA + + + 3 + 13117 + 6885 + Geneon Universal Entertainment, Studio Deen, Frontier Works, The Klock Worx + http://cdn.myanimelist.net/images/anime/9/49399.jpg + Hakuouki Movie 1: Kyoto Ranbu + + + 4 + 19123 + 7894 + Toei Animation, Fuji TV + http://cdn.myanimelist.net/images/anime/12/51777.jpg + One Piece: Episode of Merry - Mou Hitori no Nakama no Monogatari + + + 3 + 12517 + 6757 + Xebec, AIC + http://cdn.myanimelist.net/images/anime/13/53805.jpg + Uchuu Senkan Yamato 2199 Movie 7: Soshite Kan wa Iku + + + 5 + 20355 + 7970 + Sunrise + http://cdn.myanimelist.net/images/anime/13/54101.jpg + Zeonic Toyota Special Movie + + + 5 + 20359 + 8062 + Kodansha + http://cdn.myanimelist.net/images/anime/3/54103.jpg + Yamada-kun to 7-nin no Majo + + + 4 + 19211 + 7872 + Production I.G, Viz Media + http://cdn.myanimelist.net/images/anime/8/51509.jpg + Suisei no Gargantia Specials + + + 4 + 20035 + 8120 + J.C. Staff, Geneon Universal Entertainment + http://cdn.myanimelist.net/images/anime/9/53541.jpg + Toaru Majutsu no Index: Endymion no Kiseki Special + + + 2 + 17643 + 7687 + J.C. Staff + http://cdn.myanimelist.net/images/anime/10/47535.jpg + Little Busters! OVA + + + 3 + 15039 + 7200 + Aniplex, A-1 Pictures + http://cdn.myanimelist.net/images/anime/3/53903.jpg + Ano Hi Mita Hana no Namae wo Bokutachi wa Mada Shiranai. Movie + \ No newline at end of file diff --git a/data/db/season/2013_winter.xml b/data/db/season/2013_winter.xml index d68a31ef7..2ba4f7963 100644 --- a/data/db/season/2013_winter.xml +++ b/data/db/season/2013_winter.xml @@ -1,665 +1,759 @@ - - - - Winter 2013 - 1380404848 - - - 3 - 15785 - Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/7/45290.jpg - Inazuma Eleven Go vs Danball Senki W Movie - - - 2 - 14145 - Anpro, teamKG - http://cdn.myanimelist.net/images/anime/2/45356.jpg - Hanayaka Nari, Waga Ichizoku: Kinetograph - - - 2 - 15775 - A-1 Pictures - http://cdn.myanimelist.net/images/anime/3/53249.jpg - Saint☆Onii-san - - - 4 - 15895 - TV Tokyo - http://cdn.myanimelist.net/images/anime/4/46827.jpg - Hiyokoi (2012) - - - 2 - 15959 - Studio Deen - http://cdn.myanimelist.net/images/anime/2/44574.jpg - Nurarihyon no Mago OVA - - - 1 - 16347 - Kanaban Graphics - http://cdn.myanimelist.net/images/anime/10/54017.jpg - Usavich V - - - 5 - 16636 - AIC - http://cdn.myanimelist.net/images/anime/10/45090.jpg - Kotoura-san: Haruka no Heya - - - 4 - 16576 - TV Tokyo, NAS - http://cdn.myanimelist.net/images/anime/9/44712.jpg - Yu-Gi-Oh! Zexal Second: Midokoro Tenkomori Special - - - 4 - 18425 - - http://cdn.myanimelist.net/images/anime/2/49327.jpg - Ikeike! Momon-chan Specials - - - 3 - 12859 - Toei Animation - http://cdn.myanimelist.net/images/anime/6/44297.jpg - One Piece Film Z - - - 4 - 16239 - Toei Animation - http://cdn.myanimelist.net/images/anime/8/52463.jpg - One Piece: Episode of Luffy - Hand Island no Bouken - - - 2 - 15633 - Kinema Citrus - http://cdn.myanimelist.net/images/anime/8/42475.jpg - Code:Breaker OVA - - - 2 - 15819 - Toei Animation - http://cdn.myanimelist.net/images/anime/3/45070.jpg - Kindaichi Shounen no Jikenbo: Kuromajutsu Satsujin Jiken-hen - - - 4 - 15879 - Kyoto Animation, Lantis, Pony Canyon - http://cdn.myanimelist.net/images/anime/9/43245.jpg - Chuunibyou demo Koi ga Shitai!: Depth of Field - Ai to Nikushimi Gekijou - - - 2 - 16183 - TMS Entertainment - http://cdn.myanimelist.net/images/anime/10/44261.jpg - Lupin Shanshei - - - 5 - 17237 - CoMix Wave - http://cdn.myanimelist.net/images/anime/8/46043.jpg - Peeping Life: World History - - - 2 - 16287 - Toei Animation - http://cdn.myanimelist.net/images/anime/4/43841.jpg - One Piece: Romance Dawn - - - 2 - 13767 - - http://cdn.myanimelist.net/images/anime/2/38447.jpg - Junjou Romantica (OVA) - - - 4 - 17205 - Sunrise - http://cdn.myanimelist.net/images/anime/4/45947.jpg - Cowboy Bebop: Ein no Natsuyasumi - - - 4 - 17259 - Production I.G - http://cdn.myanimelist.net/images/anime/11/46071.jpg - Kuroko no Basket: Oshaberi Shiyokka - - - 4 - 18343 - Actas - http://cdn.myanimelist.net/images/anime/2/49297.jpg - Girls und Panzer: Fushou - Akiyama Yukari no Sensha Kouza - - - 4 - 15811 - Actas, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/7/42857.jpg - Girls und Panzer Specials - - - 1 - 16405 - Toho Company, Charaction - http://cdn.myanimelist.net/images/anime/12/45352.jpg - Boku no Imouto wa "Osaka Okan" - - - 3 - 15067 - Sunrise, Asia-Do - http://cdn.myanimelist.net/images/anime/2/40869.jpg - Kaiketsu Zorori Da-Da-Da-Daibouken! - - - 4 - 16468 - Toei Animation, NOTTV - http://cdn.myanimelist.net/images/anime/3/44410.jpg - One Piece Special: Glorious Island - - - 4 - 15989 - Lantis, Studio Gokumi, Saki Achiga-hen Production Committee, TV Tokyo Music - http://cdn.myanimelist.net/images/anime/8/45084.jpg - Saki: Achiga-hen - Episode of Side-A Specials - - - 5 - 16774 - CoMix Wave, Trigger - http://cdn.myanimelist.net/images/anime/2/47037.jpg - Inferno Cop - - - 4 - 17409 - Starchild Records, Zexcs - http://cdn.myanimelist.net/images/anime/7/46551.jpg - Sukitte Ii na yo. Specials - - - 4 - 15881 - FUNimation Entertainment, Silver Link - http://cdn.myanimelist.net/images/anime/6/46981.jpg - Onii-chan Dakedo Ai Sae Areba Kankeinai yo ne! Specials - - - 2 - 16932 - Graphinica - http://cdn.myanimelist.net/images/anime/10/45410.jpg - Drifters - - - 4 - 16912 - Strawberry Meets Pictures - http://cdn.myanimelist.net/images/anime/4/45336.jpg - gdgd Fairies 2 Episode 0 - - - 5 - 17843 - CoMix Wave, Trigger - http://cdn.myanimelist.net/images/anime/9/47569.jpg - Inferno Cop: Fact Files - - - 3 - 11737 - Aniplex, A-1 Pictures, Aniplex of America - http://cdn.myanimelist.net/images/anime/7/42005.jpg - Ao no Exorcist Movie - - - 5 - 16936 - Charaction - http://cdn.myanimelist.net/images/anime/11/45386.jpg - Boku no Imouto wa "Osaka Okan": Haishin Gentei Osaka Okan. - - - 1 - 15689 - Aniplex, Shaft, Kodansha, Aniplex of America - http://cdn.myanimelist.net/images/anime/6/45516.jpg - Nekomonogatari: Kuro - - - 4 - 17020 - Kazami Gakuen Koushiki Douga-bu - http://cdn.myanimelist.net/images/anime/4/45580.jpg - Da Capo III Special - - - 5 - 15649 - FUNimation Entertainment, Gathering - http://cdn.myanimelist.net/images/anime/6/44680.jpg - Puchimas!: Petit iDOLM@STER - - - 1 - 14355 - 8bit, Earth Star Entertainment - http://cdn.myanimelist.net/images/anime/4/43847.jpg - Yama no Susume - - - 1 - 16169 - Seven, Dream Creation - http://cdn.myanimelist.net/images/anime/7/45306.jpg - Ai Mai Mi - - - 1 - 14045 - Dogakobo, Earth Star Entertainment - http://cdn.myanimelist.net/images/anime/6/45314.jpg - Mangirl! - - - 1 - 15109 - Zexcs, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/12/42533.jpg - Cuticle Tantei Inaba - - - 1 - 14827 - Lantis, Pony Canyon, Dwango, Kazami Gakuen Koushiki Douga-bu - http://cdn.myanimelist.net/images/anime/12/44167.jpg - Da Capo III - - - 1 - 14833 - Arms, Genco, Animax - http://cdn.myanimelist.net/images/anime/4/46041.jpg - Maoyuu Maou Yuusha - - - 1 - 15119 - Artland, Genco, FUNimation Entertainment, Media Factory, Marvelous AQL, Senran Kagura Partners - http://cdn.myanimelist.net/images/anime/5/45640.jpg - Senran Kagura - - - 1 - 14941 - Satelight, Starchild Records, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/11/44450.jpg - AKB0048 Next Stage - - - 1 - 14749 - Aniplex, A-1 Pictures, Aniplex of America - http://cdn.myanimelist.net/images/anime/13/44187.jpg - Ore no Kanojo to Osananajimi ga Shuraba Sugiru - - - 1 - 14511 - Feel, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/7/53133.jpg - Minami-ke Tadaima - - - 1 - 16317 - DAX Production, Hotline - http://cdn.myanimelist.net/images/anime/5/44996.jpg - Ishida to Asakura - - - 1 - 15051 - Sunrise, Lantis, NIS America, Inc. - http://cdn.myanimelist.net/images/anime/9/44340.jpg - Love Live! School Idol Project - - - 1 - 15613 - Bandai Visual, Studio Deen, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/13/43007.jpg - Hakkenden: Touhou Hakken Ibun - - - 1 - 15085 - Brains Base, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/8/45308.jpg - Amnesia - - - 1 - 16906 - TV Tokyo, Shogakukan Productions - http://cdn.myanimelist.net/images/anime/11/45518.jpg - Line Offline: Salaryman - - - 1 - 16211 - TMS Entertainment, Nippon Columbia - http://cdn.myanimelist.net/images/anime/9/43673.jpg - Bakumatsu Gijinden Roman - - - 1 - 16005 - Manglobe, UNLIMITED Partners - http://cdn.myanimelist.net/images/anime/11/44522.jpg - Zettai Karen Children: The Unlimited - Hyoubu Kyousuke - - - 5 - 16692 - - http://cdn.myanimelist.net/images/anime/10/49439.jpg - Jigoku Youchien - - - 1 - 15751 - Ordet, LIDEN FILMS - http://cdn.myanimelist.net/images/anime/6/44858.jpg - Senyuu. - - - 1 - 16417 - Kyoto Animation, Animax, Pony Canyon, Sentai Filmworks, Animation Do - http://cdn.myanimelist.net/images/anime/4/44548.jpg - Tamako Market - - - 1 - 16726 - Strawberry Meets Pictures - http://cdn.myanimelist.net/images/anime/10/45350.jpg - gdgd Fairies 2 - - - 2 - 17277 - Sunrise - http://cdn.myanimelist.net/images/anime/4/46153.jpg - Code Geass: Soubou no Oz Picture Drama - - - 1 - 14811 - VAP, Dogakobo - http://cdn.myanimelist.net/images/anime/10/45995.jpg - GJ-bu - - - 1 - 15379 - AIC, CBC - http://cdn.myanimelist.net/images/anime/12/45700.jpg - Kotoura-san - - - 1 - 14967 - FUNimation Entertainment, Media Factory, Magic Capsule, AIC Build - http://cdn.myanimelist.net/images/anime/3/44724.jpg - Boku wa Tomodachi ga Sukunai Next - - - 1 - 14515 - Aniplex, Shaft, TBS, Sentai Filmworks - http://cdn.myanimelist.net/images/anime/4/53951.jpg - Sasami-san@Ganbaranai - - - 1 - 14283 - Aniplex, A-1 Pictures, Aniplex of America - http://cdn.myanimelist.net/images/anime/3/45935.jpg - Vividred Operation - - - 1 - 16908 - Kachidoki Studio - http://cdn.myanimelist.net/images/anime/12/45332.jpg - Ganbare! Oden-kun - - - 1 - 14397 - Madhouse Studios, VAP - http://cdn.myanimelist.net/images/anime/6/47435.jpg - Chihayafuru 2 - - - 1 - 15315 - Diomedea, Kadokawa Shoten, Nippon Columbia, flying DOG, Project No Name - http://cdn.myanimelist.net/images/anime/12/43369.jpg - Mondaiji-tachi ga Isekai kara Kuru Sou Desu yo? - - - 3 - 13271 - Madhouse Studios - http://cdn.myanimelist.net/images/anime/6/53073.jpg - Hunter x Hunter: Phantom Rouge - - - 3 - 12511 - Xebec, AIC - http://cdn.myanimelist.net/images/anime/13/45923.jpg - Uchuu Senkan Yamato 2199 Movie 4: Ginga Henkyou no Koubou - - - 1 - 15795 - TV Tokyo, SynergySP, Half H.P Studio - http://cdn.myanimelist.net/images/anime/8/45576.jpg - Beast Saga - - - 1 - 15611 - - http://cdn.myanimelist.net/images/anime/3/44566.jpg - Cardfight!! Vanguard: Link Joker-hen - - - 1 - 17147 - NHK, Fanworks - http://cdn.myanimelist.net/images/anime/13/46063.jpg - Gakkatsu! 2nd Season - - - 1 - 17115 - TV Tokyo, Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/7/45749.jpg - Pokemon Best Wishes! Season 2: Episode N - - - 5 - 15195 - Studio Deen, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/12/46073.jpg - Hetalia: The Beautiful World - - - 4 - 17871 - Trigger - http://cdn.myanimelist.net/images/anime/10/47723.jpg - Yonhyakunijuu Renpai Girl - - - 2 - 14889 - AIC, AMG MUSIC - http://cdn.myanimelist.net/images/anime/10/40905.jpg - Ebiten: Kouritsu Ebisugawa Koukou Tenmonbu OVA - - - 2 - 16319 - Silver Link - http://cdn.myanimelist.net/images/anime/5/53435.jpg - Chitose Get You!! OVA - - - 3 - 12115 - Studio 4°C, Viz Media, Yahoo! Japan - http://cdn.myanimelist.net/images/anime/12/41305.jpg - Berserk: Ougon Jidaihen III - Kourin - - - 1 - 16419 - Toei Animation - http://cdn.myanimelist.net/images/anime/13/46363.jpg - DokiDoki! Precure - - - 2 - 16395 - Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/13/44249.jpg - SKET Dance OVA - - - 1 - 17127 - Tsubasa Entertainment - http://cdn.myanimelist.net/images/anime/3/46763.jpg - Chokkyuu Hyoudai Robot Anime: Straight Title - - - 2 - 15591 - J.C. Staff - http://cdn.myanimelist.net/images/anime/8/50549.jpg - Joshiraku OVA - - - 3 - 12857 - Bones, Aniplex - http://cdn.myanimelist.net/images/anime/9/47389.jpg - Star Driver the Movie - - - 3 - 17121 - CoMix Wave - http://cdn.myanimelist.net/images/anime/9/46741.jpg - Dareka no Manazashi - - - 4 - 17535 - Satelight, Dentsu, A-1 Pictures, FUNimation Entertainment - http://cdn.myanimelist.net/images/anime/8/50101.jpg - Fairy Tail: Houou no Miko - Hajimari no Asa - - - 2 - 15391 - Hoods Entertainment - http://cdn.myanimelist.net/images/anime/7/47133.jpg - Kagaku na Yatsura - - - 4 - 16916 - Production I.G, Bandai Visual, Lantis - http://cdn.myanimelist.net/images/anime/2/45342.jpg - Kuroko no Basket: Tip Off - - - 4 - 17717 - Sunrise - http://cdn.myanimelist.net/images/anime/6/47409.jpg - Mobile Suit Gundam: The 08th MS Team - A Battle with the Third Dimension - - - 5 - 17563 - Ankama - http://cdn.myanimelist.net/images/anime/10/51987.jpg - Koutetsu no Vendetta Episode 0 - - - 3 - 11743 - J.C. Staff, Geneon Universal Entertainment, Square Enix, AT-X - http://cdn.myanimelist.net/images/anime/5/54191.jpg - Toaru Majutsu no Index: Endymion no Kiseki - - - 4 - 16119 - TYO Animations - http://cdn.myanimelist.net/images/anime/7/43489.jpg - Chouyaku Hyakuninisshu: Uta Koi. - Sake to Kikoushi ~ Murasaki Kishikibu to Kintou - - - 2 - 17157 - Studio Fantasia, Toho Company - http://cdn.myanimelist.net/images/anime/7/46247.jpg - Nozoki Ana - - - 2 - 16023 - An DerCen - http://cdn.myanimelist.net/images/anime/6/44514.jpg - Kuro to Kin no Hirakanai Kagi. - - - 1 - 17353 - Kachidoki Studio - http://cdn.myanimelist.net/images/anime/2/46523.jpg - Tenpou Suikoden Neo - + + + + Winter 2013 + 1380404848 + + + 3 + 15785 + 7309 + Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/7/45290.jpg + Inazuma Eleven Go vs Danball Senki W Movie + + + 2 + 14145 + 7088 + Anpro, teamKG + http://cdn.myanimelist.net/images/anime/2/45356.jpg + Hanayaka Nari, Waga Ichizoku: Kinetograph + + + 2 + 15775 + 7306 + A-1 Pictures + http://cdn.myanimelist.net/images/anime/3/53249.jpg + Saint☆Onii-san + + + 4 + 15895 + 7335 + TV Tokyo + http://cdn.myanimelist.net/images/anime/4/46827.jpg + Hiyokoi (2012) + + + 2 + 15959 + 7347 + Studio Deen + http://cdn.myanimelist.net/images/anime/2/44574.jpg + Nurarihyon no Mago OVA + + + 1 + 16347 + 7409 + Kanaban Graphics + http://cdn.myanimelist.net/images/anime/10/54017.jpg + Usavich V + + + 5 + 16636 + 7477 + AIC + http://cdn.myanimelist.net/images/anime/10/45090.jpg + Kotoura-san: Haruka no Heya + + + 4 + 16576 + 7466 + TV Tokyo, NAS + http://cdn.myanimelist.net/images/anime/9/44712.jpg + Yu-Gi-Oh! Zexal Second: Midokoro Tenkomori Special + + + 4 + 18425 + + + http://cdn.myanimelist.net/images/anime/2/49327.jpg + Ikeike! Momon-chan Specials + + + 3 + 12859 + 6827 + Toei Animation + http://cdn.myanimelist.net/images/anime/6/44297.jpg + One Piece Film Z + + + 4 + 16239 + 7390 + Toei Animation + http://cdn.myanimelist.net/images/anime/8/52463.jpg + One Piece: Episode of Luffy - Hand Island no Bouken + + + 2 + 15633 + 7286 + Kinema Citrus + http://cdn.myanimelist.net/images/anime/8/42475.jpg + Code:Breaker OVA + + + 2 + 15819 + 7318 + Toei Animation + http://cdn.myanimelist.net/images/anime/3/45070.jpg + Kindaichi Shounen no Jikenbo: Kuromajutsu Satsujin Jiken-hen + + + 4 + 15879 + 7330 + Kyoto Animation, Lantis, Pony Canyon + http://cdn.myanimelist.net/images/anime/9/43245.jpg + Chuunibyou demo Koi ga Shitai!: Depth of Field - Ai to Nikushimi Gekijou + + + 2 + 16183 + 7383 + TMS Entertainment + http://cdn.myanimelist.net/images/anime/10/44261.jpg + Lupin Shanshei + + + 5 + 17237 + 7614 + CoMix Wave + http://cdn.myanimelist.net/images/anime/8/46043.jpg + Peeping Life: World History + + + 2 + 16287 + 7402 + Toei Animation + http://cdn.myanimelist.net/images/anime/4/43841.jpg + One Piece: Romance Dawn + + + 2 + 13767 + 7024 + + http://cdn.myanimelist.net/images/anime/2/38447.jpg + Junjou Romantica (OVA) + + + 4 + 17205 + 7604 + Sunrise + http://cdn.myanimelist.net/images/anime/4/45947.jpg + Cowboy Bebop: Ein no Natsuyasumi + + + 4 + 17259 + 7620 + Production I.G + http://cdn.myanimelist.net/images/anime/11/46071.jpg + Kuroko no Basket: Oshaberi Shiyokka + + + 4 + 18343 + 7811 + Actas + http://cdn.myanimelist.net/images/anime/2/49297.jpg + Girls und Panzer: Fushou - Akiyama Yukari no Sensha Kouza + + + 4 + 15811 + 7315 + Actas, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/7/42857.jpg + Girls und Panzer Specials + + + 1 + 16405 + 7423 + Toho Company, Charaction + http://cdn.myanimelist.net/images/anime/12/45352.jpg + Boku no Imouto wa "Osaka Okan" + + + 3 + 15067 + 7206 + Sunrise, Asia-Do + http://cdn.myanimelist.net/images/anime/2/40869.jpg + Kaiketsu Zorori Da-Da-Da-Daibouken! + + + 4 + 16468 + 7434 + Toei Animation, NOTTV + http://cdn.myanimelist.net/images/anime/3/44410.jpg + One Piece Special: Glorious Island + + + 4 + 15989 + 7353 + Lantis, Studio Gokumi, Saki Achiga-hen Production Committee, TV Tokyo Music + http://cdn.myanimelist.net/images/anime/8/45084.jpg + Saki: Achiga-hen - Episode of Side-A Specials + + + 5 + 16774 + 7511 + CoMix Wave, Trigger + http://cdn.myanimelist.net/images/anime/2/47037.jpg + Inferno Cop + + + 4 + 17409 + 7779 + Starchild Records, Zexcs + http://cdn.myanimelist.net/images/anime/7/46551.jpg + Sukitte Ii na yo. Specials + + + 4 + 15881 + 7331 + FUNimation Entertainment, Silver Link + http://cdn.myanimelist.net/images/anime/6/46981.jpg + Onii-chan Dakedo Ai Sae Areba Kankeinai yo ne! Specials + + + 2 + 16932 + 7555 + Graphinica + http://cdn.myanimelist.net/images/anime/10/45410.jpg + Drifters + + + 4 + 16912 + 7550 + Strawberry Meets Pictures + http://cdn.myanimelist.net/images/anime/4/45336.jpg + gdgd Fairies 2 Episode 0 + + + 5 + 17843 + 7736 + CoMix Wave, Trigger + http://cdn.myanimelist.net/images/anime/9/47569.jpg + Inferno Cop: Fact Files + + + 3 + 11737 + 6580 + Aniplex, A-1 Pictures, Aniplex of America + http://cdn.myanimelist.net/images/anime/7/42005.jpg + Ao no Exorcist Movie + + + 5 + 16936 + 7557 + Charaction + http://cdn.myanimelist.net/images/anime/11/45386.jpg + Boku no Imouto wa "Osaka Okan": Haishin Gentei Osaka Okan. + + + 1 + 15689 + 7294 + Aniplex, Shaft, Kodansha, Aniplex of America + http://cdn.myanimelist.net/images/anime/6/45516.jpg + Nekomonogatari: Kuro + + + 4 + 17020 + 7569 + Kazami Gakuen Koushiki Douga-bu + http://cdn.myanimelist.net/images/anime/4/45580.jpg + Da Capo III Special + + + 5 + 15649 + 7287 + FUNimation Entertainment, Gathering + http://cdn.myanimelist.net/images/anime/6/44680.jpg + Puchimas!: Petit iDOLM@STER + + + 1 + 14355 + 7117 + 8bit, Earth Star Entertainment + http://cdn.myanimelist.net/images/anime/4/43847.jpg + Yama no Susume + + + 1 + 16169 + 7382 + Seven, Dream Creation + http://cdn.myanimelist.net/images/anime/7/45306.jpg + Ai Mai Mi + + + 1 + 14045 + 7071 + Dogakobo, Earth Star Entertainment + http://cdn.myanimelist.net/images/anime/6/45314.jpg + Mangirl! + + + 1 + 15109 + 7213 + Zexcs, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/12/42533.jpg + Cuticle Tantei Inaba + + + 1 + 14827 + 7172 + Lantis, Pony Canyon, Dwango, Kazami Gakuen Koushiki Douga-bu + http://cdn.myanimelist.net/images/anime/12/44167.jpg + Da Capo III + + + 1 + 14833 + 7174 + Arms, Genco, Animax + http://cdn.myanimelist.net/images/anime/4/46041.jpg + Maoyuu Maou Yuusha + + + 1 + 15119 + 7216 + Artland, Genco, FUNimation Entertainment, Media Factory, Marvelous AQL, Senran Kagura Partners + http://cdn.myanimelist.net/images/anime/5/45640.jpg + Senran Kagura + + + 1 + 14941 + 7184 + Satelight, Starchild Records, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/11/44450.jpg + AKB0048 Next Stage + + + 1 + 14749 + 7162 + Aniplex, A-1 Pictures, Aniplex of America + http://cdn.myanimelist.net/images/anime/13/44187.jpg + Ore no Kanojo to Osananajimi ga Shuraba Sugiru + + + 1 + 14511 + 7128 + Feel, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/7/53133.jpg + Minami-ke Tadaima + + + 1 + 16317 + 7404 + DAX Production, Hotline + http://cdn.myanimelist.net/images/anime/5/44996.jpg + Ishida to Asakura + + + 1 + 15051 + 7203 + Sunrise, Lantis, NIS America, Inc. + http://cdn.myanimelist.net/images/anime/9/44340.jpg + Love Live! School Idol Project + + + 1 + 15613 + 7284 + Bandai Visual, Studio Deen, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/13/43007.jpg + Hakkenden: Touhou Hakken Ibun + + + 1 + 15085 + 7209 + Brains Base, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/8/45308.jpg + Amnesia + + + 1 + 16906 + 7547 + TV Tokyo, Shogakukan Productions + http://cdn.myanimelist.net/images/anime/11/45518.jpg + Line Offline: Salaryman + + + 1 + 16211 + 7388 + TMS Entertainment, Nippon Columbia + http://cdn.myanimelist.net/images/anime/9/43673.jpg + Bakumatsu Gijinden Roman + + + 1 + 16005 + 7356 + Manglobe, UNLIMITED Partners + http://cdn.myanimelist.net/images/anime/11/44522.jpg + Zettai Karen Children: The Unlimited - Hyoubu Kyousuke + + + 5 + 16692 + 7491 + + http://cdn.myanimelist.net/images/anime/10/49439.jpg + Jigoku Youchien + + + 1 + 15751 + 7303 + Ordet, LIDEN FILMS + http://cdn.myanimelist.net/images/anime/6/44858.jpg + Senyuu. + + + 1 + 16417 + 7425 + Kyoto Animation, Animax, Pony Canyon, Sentai Filmworks, Animation Do + http://cdn.myanimelist.net/images/anime/4/44548.jpg + Tamako Market + + + 1 + 16726 + 7498 + Strawberry Meets Pictures + http://cdn.myanimelist.net/images/anime/10/45350.jpg + gdgd Fairies 2 + + + 2 + 17277 + 7627 + Sunrise + http://cdn.myanimelist.net/images/anime/4/46153.jpg + Code Geass: Soubou no Oz Picture Drama + + + 1 + 14811 + 7168 + VAP, Dogakobo + http://cdn.myanimelist.net/images/anime/10/45995.jpg + GJ-bu + + + 1 + 15379 + 7245 + AIC, CBC + http://cdn.myanimelist.net/images/anime/12/45700.jpg + Kotoura-san + + + 1 + 14967 + 7190 + FUNimation Entertainment, Media Factory, Magic Capsule, AIC Build + http://cdn.myanimelist.net/images/anime/3/44724.jpg + Boku wa Tomodachi ga Sukunai Next + + + 1 + 14515 + 7130 + Aniplex, Shaft, TBS, Sentai Filmworks + http://cdn.myanimelist.net/images/anime/4/53951.jpg + Sasami-san@Ganbaranai + + + 1 + 14283 + 7108 + Aniplex, A-1 Pictures, Aniplex of America + http://cdn.myanimelist.net/images/anime/3/45935.jpg + Vividred Operation + + + 1 + 16908 + 7548 + Kachidoki Studio + http://cdn.myanimelist.net/images/anime/12/45332.jpg + Ganbare! Oden-kun + + + 1 + 14397 + 7123 + Madhouse Studios, VAP + http://cdn.myanimelist.net/images/anime/6/47435.jpg + Chihayafuru 2 + + + 1 + 15315 + 7239 + Diomedea, Kadokawa Shoten, Nippon Columbia, flying DOG, Project No Name + http://cdn.myanimelist.net/images/anime/12/43369.jpg + Mondaiji-tachi ga Isekai kara Kuru Sou Desu yo? + + + 3 + 13271 + 6930 + Madhouse Studios + http://cdn.myanimelist.net/images/anime/6/53073.jpg + Hunter x Hunter: Phantom Rouge + + + 3 + 12511 + 6754 + Xebec, AIC + http://cdn.myanimelist.net/images/anime/13/45923.jpg + Uchuu Senkan Yamato 2199 Movie 4: Ginga Henkyou no Koubou + + + 1 + 15795 + 7312 + TV Tokyo, SynergySP, Half H.P Studio + http://cdn.myanimelist.net/images/anime/8/45576.jpg + Beast Saga + + + 1 + 15611 + 7283 + TV Tokyo, Dentsu, Sotsu Agency, TMS Entertainment, Bushiroad Inc. + http://cdn.myanimelist.net/images/anime/3/44566.jpg + Cardfight!! Vanguard: Link Joker-hen + + + 1 + 17147 + 7594 + NHK, Fanworks + http://cdn.myanimelist.net/images/anime/13/46063.jpg + Gakkatsu! 2nd Season + + + 1 + 17115 + 7586 + TV Tokyo, Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/7/45749.jpg + Pokemon Best Wishes! Season 2: Episode N + + + 5 + 15195 + 7226 + Studio Deen, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/12/46073.jpg + Hetalia: The Beautiful World + + + 4 + 17871 + 7825 + Trigger + http://cdn.myanimelist.net/images/anime/10/47723.jpg + Yonhyakunijuu Renpai Girl + + + 2 + 14889 + 7179 + AIC, AMG MUSIC + http://cdn.myanimelist.net/images/anime/10/40905.jpg + Ebiten: Kouritsu Ebisugawa Koukou Tenmonbu OVA + + + 2 + 16319 + 7405 + Silver Link + http://cdn.myanimelist.net/images/anime/5/53435.jpg + Chitose Get You!! OVA + + + 3 + 12115 + 6665 + Studio 4°C, Viz Media, Yahoo! Japan + http://cdn.myanimelist.net/images/anime/12/41305.jpg + Berserk: Ougon Jidaihen III - Kourin + + + 1 + 16419 + 7426 + Toei Animation + http://cdn.myanimelist.net/images/anime/13/46363.jpg + DokiDoki! Precure + + + 2 + 16395 + 7420 + Tatsunoko Productions + http://cdn.myanimelist.net/images/anime/13/44249.jpg + SKET Dance OVA + + + 1 + 17127 + 7589 + Tsubasa Entertainment + http://cdn.myanimelist.net/images/anime/3/46763.jpg + Chokkyuu Hyoudai Robot Anime: Straight Title + + + 2 + 15591 + 7279 + J.C. Staff + http://cdn.myanimelist.net/images/anime/8/50549.jpg + Joshiraku OVA + + + 3 + 12857 + 6826 + Bones, Aniplex + http://cdn.myanimelist.net/images/anime/9/47389.jpg + Star Driver the Movie + + + 3 + 17121 + 7588 + CoMix Wave + http://cdn.myanimelist.net/images/anime/9/46741.jpg + Dareka no Manazashi + + + 4 + 17535 + 7810 + Satelight, Dentsu, A-1 Pictures, FUNimation Entertainment + http://cdn.myanimelist.net/images/anime/8/50101.jpg + Fairy Tail: Houou no Miko - Hajimari no Asa + + + 2 + 15391 + 7247 + Hoods Entertainment + http://cdn.myanimelist.net/images/anime/7/47133.jpg + Kagaku na Yatsura + + + 4 + 16916 + 7552 + Production I.G, Bandai Visual, Lantis + http://cdn.myanimelist.net/images/anime/2/45342.jpg + Kuroko no Basket: Tip Off + + + 4 + 17717 + + Sunrise + http://cdn.myanimelist.net/images/anime/6/47409.jpg + Mobile Suit Gundam: The 08th MS Team - A Battle with the Third Dimension + + + 5 + 17563 + + Ankama + http://cdn.myanimelist.net/images/anime/10/51987.jpg + Koutetsu no Vendetta Episode 0 + + + 3 + 11743 + 6583 + J.C. Staff, Geneon Universal Entertainment, Square Enix, AT-X + http://cdn.myanimelist.net/images/anime/5/54191.jpg + Toaru Majutsu no Index: Endymion no Kiseki + + + 4 + 16119 + 7374 + TYO Animations + http://cdn.myanimelist.net/images/anime/7/43489.jpg + Chouyaku Hyakuninisshu: Uta Koi. - Sake to Kikoushi ~ Murasaki Kishikibu to Kintou + + + 2 + 17157 + 7596 + Studio Fantasia, Toho Company + http://cdn.myanimelist.net/images/anime/7/46247.jpg + Nozoki Ana + + + 2 + 16023 + 7361 + An DerCen + http://cdn.myanimelist.net/images/anime/6/44514.jpg + Kuro to Kin no Hirakanai Kagi. + + + 1 + 17353 + 7720 + Kachidoki Studio + http://cdn.myanimelist.net/images/anime/2/46523.jpg + Tenpou Suikoden Neo + \ No newline at end of file diff --git a/data/db/season/2014_spring.xml b/data/db/season/2014_spring.xml index 15bf8d0aa..dbb73c069 100644 --- a/data/db/season/2014_spring.xml +++ b/data/db/season/2014_spring.xml @@ -1,651 +1,743 @@ - + Spring 2014 - 1395314849 + 1395314849 - 3 - 12783 + 3 + 12783 + 6812 Nippon Columbia, Production IMS - http://cdn.myanimelist.net/images/anime/3/58059.jpg - Sora no Otoshimono Final: Eternal My Master + http://cdn.myanimelist.net/images/anime/3/58059.jpg + Sora no Otoshimono Final: Eternal My Master - 3 - 13119 + 3 + 13119 + 6886 Studio Deen - http://cdn.myanimelist.net/images/anime/9/57843.jpg - Hakuouki Movie 2: Shikon Soukyuu + http://cdn.myanimelist.net/images/anime/9/57843.jpg + Hakuouki Movie 2: Shikon Soukyuu - 2 - 13843 + 2 + 13843 + 7046 Anpro - http://cdn.myanimelist.net/images/anime/4/38463.jpg - Wild Adapter + http://cdn.myanimelist.net/images/anime/4/38463.jpg + Wild Adapter - 2 - 18851 + 2 + 18851 + 7861 Silver Link - http://cdn.myanimelist.net/images/anime/11/50659.jpg - Fate/kaleid liner Prisma☆Illya (2014) + http://cdn.myanimelist.net/images/anime/11/50659.jpg + Fate/kaleid liner Prisma☆Illya OVA - 1 - 19111 + 1 + 19111 + 7871 Sunrise - http://cdn.myanimelist.net/images/anime/10/59101.jpg - Love Live! School Idol Project 2nd Season + http://cdn.myanimelist.net/images/anime/10/59101.jpg + Love Live! School Idol Project 2nd Season - 1 - 19163 + 1 + 19163 + 7864 AIC Plus+, Nippon Columbia - http://cdn.myanimelist.net/images/anime/6/59227.jpg - Date A Live II + http://cdn.myanimelist.net/images/anime/6/59227.jpg + Date A Live II - 1 - 19429 + 1 + 19429 + 7844 Diomedea - http://cdn.myanimelist.net/images/anime/10/59009.jpg - Akuma no Riddle + http://cdn.myanimelist.net/images/anime/10/59009.jpg + Akuma no Riddle - 3 - 19645 + 3 + 19645 + 7902 Shin-Ei Animation - http://cdn.myanimelist.net/images/anime/10/52589.jpg - Doraemon: New Nobita's Great Demon - Peko and the Exploration Party of Five + http://cdn.myanimelist.net/images/anime/10/52589.jpg + Doraemon: New Nobita's Great Demon - Peko and the Exploration Party of Five - 1 - 19685 + 1 + 19685 + 8011 Hoods Entertainment, The Klock Worx - http://cdn.myanimelist.net/images/anime/4/56513.jpg - Kanojo ga Flag wo Oraretara + http://cdn.myanimelist.net/images/anime/4/56513.jpg + Kanojo ga Flag wo Oraretara - 1 - 19775 + 1 + 19775 + 7768 Starchild Records, Polygon Pictures - http://cdn.myanimelist.net/images/anime/12/53257.jpg - Sidonia no Kishi + http://cdn.myanimelist.net/images/anime/12/53257.jpg + Sidonia no Kishi - 1 - 19815 + 1 + 19815 + 7880 Madhouse, Media Factory - http://cdn.myanimelist.net/images/anime/12/59115.jpg - No Game No Life + http://cdn.myanimelist.net/images/anime/12/59115.jpg + No Game No Life - 3 - 20371 + 3 + 20371 + 8193 Studio Deen - http://cdn.myanimelist.net/images/anime/7/56467.jpg - Sekaiichi Hatsukoi Movie: Yokozawa Takafumi no Baai + http://cdn.myanimelist.net/images/anime/7/56467.jpg + Sekaiichi Hatsukoi Movie: Yokozawa Takafumi no Baai - 2 - 20479 + 2 + 20479 + 8071 Frontier Works, Idea Factory, David Production - http://cdn.myanimelist.net/images/anime/7/54393.jpg - Choujigen Game Neptune: The Animation OVA + http://cdn.myanimelist.net/images/anime/7/54393.jpg + Choujigen Game Neptune: The Animation OVA - 2 - 20545 + 2 + 20545 + 8029 Asread - http://cdn.myanimelist.net/images/anime/7/54825.jpg - Yuusha ni Narenakatta Ore wa Shibushibu Shuushoku wo Ketsui Shimashita. OVA + http://cdn.myanimelist.net/images/anime/7/54825.jpg + Yuusha ni Narenakatta Ore wa Shibushibu Shuushoku wo Ketsui Shimashita. OVA - 1 - 20583 + 1 + 20583 + 8133 Production I.G - http://cdn.myanimelist.net/images/anime/13/55005.jpg - Haikyuu!! + http://cdn.myanimelist.net/images/anime/13/55005.jpg + Haikyuu!! - 1 - 20785 + 1 + 20785 + 8053 Madhouse, Aniplex - http://cdn.myanimelist.net/images/anime/13/55413.jpg - Mahouka Koukou no Rettousei + http://cdn.myanimelist.net/images/anime/13/55413.jpg + Mahouka Koukou no Rettousei - 1 - 20787 + 1 + 20787 + 8052 Kinema Citrus - http://cdn.myanimelist.net/images/anime/6/57947.jpg - Black Bullet + http://cdn.myanimelist.net/images/anime/6/57947.jpg + Black Bullet - 4 - 20815 + 4 + 20815 + 8192 Studio Deen - http://cdn.myanimelist.net/images/anime/5/55723.jpg - Sekaiichi Hatsukoi: Valentine-hen + http://cdn.myanimelist.net/images/anime/5/55723.jpg + Sekaiichi Hatsukoi: Valentine-hen - 1 - 20853 + 1 + 20853 + 8008 Bones, flying DOG - http://cdn.myanimelist.net/images/anime/8/58789.jpg - Hitsugi no Chaika + http://cdn.myanimelist.net/images/anime/8/58789.jpg + Hitsugi no Chaika - 3 - 20889 - Studio 4°C - http://cdn.myanimelist.net/images/anime/6/55167.jpg - Kuro no Sumika -Chronus- + 3 + 20889 + 8106 + Studio 4°C + http://cdn.myanimelist.net/images/anime/6/55167.jpg + Kuro no Sumika -Chronus- - 1 - 20899 + 1 + 20899 + 8063 David Production, Warner Bros. - http://cdn.myanimelist.net/images/anime/11/55267.jpg - JoJo's Bizarre Adventure: Stardust Crusaders + http://cdn.myanimelist.net/images/anime/11/55267.jpg + JoJo's Bizarre Adventure: Stardust Crusaders - 3 - 20903 + 3 + 20903 + 8105 Ultra Super Pictures - http://cdn.myanimelist.net/images/anime/2/55197.jpg - Harmonie + http://cdn.myanimelist.net/images/anime/2/55197.jpg + Harmonie - 3 - 20907 + 3 + 20907 + 8107 A-1 Pictures - http://cdn.myanimelist.net/images/anime/5/55213.jpg - Ookii 1 Nensei to Chiisana 2 Nensei + http://cdn.myanimelist.net/images/anime/5/55213.jpg + Ookii 1 Nensei to Chiisana 2 Nensei - 2 - 20939 + 2 + 20939 + 8188 Diomedea, DAX Production, Studio Jack - http://cdn.myanimelist.net/images/anime/12/55279.jpg - Ore no Nounai Sentakushi ga, Gakuen Love Comedy wo Zenryoku de Jama Shiteiru OVA + http://cdn.myanimelist.net/images/anime/12/55279.jpg + Ore no Nounai Sentakushi ga, Gakuen Love Comedy wo Zenryoku de Jama Shiteiru OVA - 3 - 20961 + 3 + 20961 + 8108 Shin-Ei Animation - http://cdn.myanimelist.net/images/anime/2/55321.jpg - Parol no Mirai Shima + http://cdn.myanimelist.net/images/anime/2/55321.jpg + Parol no Mirai Shima - 3 - 20963 + 3 + 20963 + 8288 Benesse Corporation - http://cdn.myanimelist.net/images/anime/5/55323.jpg - Shimajirou to Kujira no Uta + http://cdn.myanimelist.net/images/anime/5/55323.jpg + Shimajirou to Kujira no Uta - 1 - 20971 + 1 + 20971 + 8012 Koei, TYO Animations - http://cdn.myanimelist.net/images/anime/2/58933.jpg - Kiniro no Corda: Blue♪Sky + http://cdn.myanimelist.net/images/anime/2/58933.jpg + Kiniro no Corda: Blue♪Sky - 1 - 21013 + 1 + 21013 + 8284 Toei Animation - http://cdn.myanimelist.net/images/anime/13/56857.jpg - Marvel Disk Wars: The Avengers + http://cdn.myanimelist.net/images/anime/13/56857.jpg + Marvel Disk Wars: The Avengers - 3 - 21031 + 3 + 21031 + 8252 Toei Animation - http://cdn.myanimelist.net/images/anime/9/56373.jpg - Precure All Stars New Stage 3: Eien no Tomodachi + http://cdn.myanimelist.net/images/anime/9/56373.jpg + Precure All Stars New Stage 3: Eien no Tomodachi - 1 - 21033 + 1 + 21033 + 7877 Media Factory, Kadokawa Shoten, C-Station - http://cdn.myanimelist.net/images/anime/10/58237.jpg - Seikoku no Dragonar + http://cdn.myanimelist.net/images/anime/10/58237.jpg + Seikoku no Dragonar - 5 - 21073 + 5 + 21073 + 8405 Gathering - http://cdn.myanimelist.net/images/anime/6/55661.jpg - Puchimas!!: Petit Petit iDOLM@STER + http://cdn.myanimelist.net/images/anime/6/55661.jpg + Puchimas!!: Petit Petit iDOLM@STER - 1 - 21167 + 1 + 21167 + 8091 Studio Gokumi - http://cdn.myanimelist.net/images/anime/3/58509.jpg - Escha & Logy no Atelier: Tasogare no Sora no Renkinjutsushi + http://cdn.myanimelist.net/images/anime/3/58509.jpg + Escha & Logy no Atelier: Tasogare no Sora no Renkinjutsushi - 1 - 21185 + 1 + 21185 + 8093 Studio Pierrot - http://cdn.myanimelist.net/images/anime/5/56113.jpg - Baby Steps + http://cdn.myanimelist.net/images/anime/5/56113.jpg + Baby Steps - 1 - 21273 + 1 + 21273 + 8095 White Fox - http://cdn.myanimelist.net/images/anime/7/56289.jpg - Gochuumon wa Usagi Desu ka? + http://cdn.myanimelist.net/images/anime/7/56289.jpg + Gochuumon wa Usagi Desu ka? - 1 - 21327 + 1 + 21327 + 8096 Brains Base - http://cdn.myanimelist.net/images/anime/8/58227.jpg - Isshuukan Friends. + http://cdn.myanimelist.net/images/anime/8/58227.jpg + Isshuukan Friends. - 3 - 21395 + 3 + 21395 + 8286 Shin-Ei Animation - http://cdn.myanimelist.net/images/anime/6/56479.jpg - Crayon Shin-chan Movie 22: Gachinko! Gyakushuu no Robo To-chan + http://cdn.myanimelist.net/images/anime/6/56479.jpg + Crayon Shin-chan Movie 22: Gachinko! Gyakushuu no Robo To-chan - 1 - 21405 + 1 + 21405 + 8094 Brains Base - http://cdn.myanimelist.net/images/anime/4/58903.jpg - Bokura wa Minna Kawaisou + http://cdn.myanimelist.net/images/anime/4/58903.jpg + Bokura wa Minna Kawaisou - 3 - 21419 + 3 + 21419 + 8186 TMS Entertainment - http://cdn.myanimelist.net/images/anime/3/56529.jpg - Detective Conan Movie 18: The Sniper from Another Dimension + http://cdn.myanimelist.net/images/anime/3/56529.jpg + Detective Conan Movie 18: The Sniper from Another Dimension - 1 - 21421 + 1 + 21421 + 8380 Gainax - http://cdn.myanimelist.net/images/anime/10/56835.jpg - Mahou Shoujo Taisen + http://cdn.myanimelist.net/images/anime/10/56835.jpg + Mahou Shoujo Taisen - 1 - 21431 + 1 + 21431 + 7996 VAP, Arms - http://cdn.myanimelist.net/images/anime/4/56657.jpg - Gokukoku no Brynhildr + http://cdn.myanimelist.net/images/anime/4/56657.jpg + Gokukoku no Brynhildr - 4 - 21497 + 4 + 21497 + 8194 TYO Animations - http://cdn.myanimelist.net/images/anime/7/56703.jpg - Sengoku Musou SP: Sanada no Shou + http://cdn.myanimelist.net/images/anime/7/56703.jpg + Sengoku Musou SP: Sanada no Shou - 1 - 21507 + 1 + 21507 + 8151 Bones - http://cdn.myanimelist.net/images/anime/10/56815.jpg - Soul Eater Not! + http://cdn.myanimelist.net/images/anime/10/56815.jpg + Soul Eater Not! - 1 - 21561 + 1 + 21561 + 8162 A-1 Pictures - http://cdn.myanimelist.net/images/anime/9/58787.jpg - Ryuugajou Nanana no Maizoukin + http://cdn.myanimelist.net/images/anime/9/58787.jpg + Ryuugajou Nanana no Maizoukin - 1 - 21563 + 1 + 21563 + 8160 Brains Base - http://cdn.myanimelist.net/images/anime/7/58929.jpg - Kamigami no Asobi + http://cdn.myanimelist.net/images/anime/7/58929.jpg + Kamigami no Asobi - 1 - 21603 + 1 + 21603 + 8000 Aniplex, Shaft - http://cdn.myanimelist.net/images/anime/5/57329.jpg - Mekakucity Actors + http://cdn.myanimelist.net/images/anime/5/57329.jpg + Mekakucity Actors - 4 - 21635 + 4 + 21635 + 8007 Doga Kobo - http://cdn.myanimelist.net/images/anime/6/56997.jpg - GJ-bu@ + http://cdn.myanimelist.net/images/anime/6/56997.jpg + GJ-bu@ - 1 - 21639 + 1 + 21639 + 8126 TV Tokyo - http://cdn.myanimelist.net/images/anime/12/58233.jpg - Yu-Gi-Oh! Arc-V + http://cdn.myanimelist.net/images/anime/12/58233.jpg + Yu-Gi-Oh! Arc-V - 3 - 21647 + 3 + 21647 + 8135 Kyoto Animation - http://cdn.myanimelist.net/images/anime/12/58149.jpg - Tamako Love Story + http://cdn.myanimelist.net/images/anime/12/58149.jpg + Tamako Love Story - 2 - 21649 + 2 + 21649 + 8140 Starchild Records, Zexcs - http://cdn.myanimelist.net/images/anime/12/57009.jpg - Mitsuwano + http://cdn.myanimelist.net/images/anime/9/58747.jpg + Mitsuwano - 1 - 21671 + 1 + 21671 + 8129 Sunrise - http://cdn.myanimelist.net/images/anime/12/58163.jpg - Keroro + http://cdn.myanimelist.net/images/anime/12/58163.jpg + Keroro - 1 - 21677 + 1 + 21677 + 7987 Bones - http://cdn.myanimelist.net/images/anime/7/59291.jpg - Captain Earth + http://cdn.myanimelist.net/images/anime/7/59291.jpg + Captain Earth - 3 - 21707 + 3 + 21707 + 8287 TV Tokyo, Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/8/58503.jpg - Pretty Rhythm All Stars Selection Prism Show☆Best Ten Movie + http://cdn.myanimelist.net/images/anime/8/58503.jpg + Pretty Rhythm All Stars Selection Prism Show☆Best Ten Movie - 1 - 21729 + 1 + 21729 + 8264 Dentsu, Sotsu Agency, TMS Entertainment, Bushiroad Inc. - http://cdn.myanimelist.net/images/anime/8/59385.jpg - Cardfight!! Vanguard: Legion Mate-hen + http://cdn.myanimelist.net/images/anime/8/59385.jpg + Cardfight!! Vanguard: Legion Mate-hen - 4 - 21781 - - http://cdn.myanimelist.net/images/anime/11/59507.jpg - Tsubasa to Hotaru + 4 + 21781 + 8164 + + http://cdn.myanimelist.net/images/anime/11/59507.jpg + Tsubasa to Hotaru - 1 - 21809 + 1 + 21809 + 8163 Genco, TNK - http://cdn.myanimelist.net/images/anime/7/57449.jpg - Kenzen Robo Daimidaler + http://cdn.myanimelist.net/images/anime/7/57449.jpg + Kenzen Robo Daimidaler - 1 - 21821 + 1 + 21821 + J.C.Staff, A.C.G.T. - http://cdn.myanimelist.net/images/anime/4/57445.jpg - Fuuun Ishin Dai☆Shogun + http://cdn.myanimelist.net/images/anime/4/57445.jpg + Fuuun Ishin Dai☆Shogun - 1 - 21835 + 1 + 21835 + 8283 Toei Animation - http://cdn.myanimelist.net/images/anime/11/57483.jpg - Majin Bone + http://cdn.myanimelist.net/images/anime/11/57483.jpg + Majin Bone - 1 - 21863 + 1 + 21863 + 8148 Zexcs - http://cdn.myanimelist.net/images/anime/10/57517.jpg - Mangaka-san to Assistant-san to + http://cdn.myanimelist.net/images/anime/10/57517.jpg + Mangaka-san to Assistant-san to - 4 - 21879 + 4 + 21879 + 8196 A-1 Pictures - http://cdn.myanimelist.net/images/anime/13/57589.jpg - Sword Art Online: Sword Art Offline - Extra Edition + http://cdn.myanimelist.net/images/anime/13/57589.jpg + Sword Art Online: Sword Art Offline - Extra Edition - 1 - 21939 + 1 + 21939 + 8204 Artland, Aniplex - http://cdn.myanimelist.net/images/anime/13/58533.jpg - Mushishi Zoku Shou + http://cdn.myanimelist.net/images/anime/13/58533.jpg + Mushishi Zoku Shou - 1 - 22043 + 1 + 22043 + 8203 A-1 Pictures, Bridge - http://cdn.myanimelist.net/images/anime/8/59313.jpg - Fairy Tail (2014) + http://cdn.myanimelist.net/images/anime/8/59313.jpg + Fairy Tail (2014) - 1 - 22051 + 1 + 22051 + 8275 Gainax - http://cdn.myanimelist.net/images/anime/12/58235.jpg - Sudden Death + http://cdn.myanimelist.net/images/anime/12/58235.jpg + Sudden Death - 2 - 22071 + 2 + 22071 + 8253 Doga Kobo, Toho Company, DAX Production, Mikakunin de Shinkoukei Production Committee - http://cdn.myanimelist.net/images/anime/13/57949.jpg - Mikakunin de Shinkoukei: Mite. Are ga Watashitachi no Tomatteiru Ryokan yo. + http://cdn.myanimelist.net/images/anime/13/57949.jpg + Mikakunin de Shinkoukei: Mite. Are ga Watashitachi no Tomatteiru Ryokan yo. - 2 - 22097 + 2 + 22097 + 8256 A-1 Pictures - http://cdn.myanimelist.net/images/anime/9/57989.jpg - Magi: Sinbad no Bouken + http://cdn.myanimelist.net/images/anime/9/57989.jpg + Magi: Sinbad no Bouken - 1 - 22099 + 1 + 22099 + 8282 Sega - http://cdn.myanimelist.net/images/anime/13/59089.jpg - Hero Bank + http://cdn.myanimelist.net/images/anime/13/59089.jpg + Hero Bank - 1 - 22101 + 1 + 22101 + 8258 Studio Pierrot - http://cdn.myanimelist.net/images/anime/4/59259.jpg - Soredemo Sekai wa Utsukushii + http://cdn.myanimelist.net/images/anime/4/59259.jpg + Soredemo Sekai wa Utsukushii - 1 - 22123 + 1 + 22123 + 8263 DAX Production, Seven, Dream Creation - http://cdn.myanimelist.net/images/anime/6/58091.jpg - Inugami-san to Nekoyama-san + http://cdn.myanimelist.net/images/anime/6/58091.jpg + Inugami-san to Nekoyama-san - 1 - 22135 + 1 + 22135 + 8262 Aniplex, Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/10/58041.jpg - Ping Pong The Animation + http://cdn.myanimelist.net/images/anime/10/58041.jpg + Ping Pong The Animation - 3 - 22197 + 3 + 22197 + Shirogumi - http://cdn.myanimelist.net/images/anime/4/59177.jpg - Yuuto-kun ga Iku Movie + http://cdn.myanimelist.net/images/anime/4/59177.jpg + Yuuto-kun ga Iku Movie - 1 - 22215 - - http://cdn.myanimelist.net/images/anime/9/59925.jpg - Pretty Rhythm: All Star Selection + 1 + 22215 + + + http://cdn.myanimelist.net/images/anime/9/59925.jpg + Pretty Rhythm: All Star Selection - 1 - 22273 + 1 + 22273 + 8285 J.C.Staff, Warner Bros. - http://cdn.myanimelist.net/images/anime/7/59113.jpg - Selector Infected WIXOSS + http://cdn.myanimelist.net/images/anime/7/59113.jpg + Selector Infected WIXOSS - 1 - 22381 + 1 + 22381 + NHK Enterprises, Cyclone Graphics inc - http://cdn.myanimelist.net/images/anime/9/59041.jpg - Nandaka Velonica + http://cdn.myanimelist.net/images/anime/9/59041.jpg + Nandaka Velonica - 1 - 22433 + 1 + 22433 + 8281 Production I.G, Bandai Visual, Xebec, Lantis - http://cdn.myanimelist.net/images/anime/7/58635.jpg - Break Blade (TV) + http://cdn.myanimelist.net/images/anime/7/58635.jpg + Break Blade (TV) - 2 - 22463 - Production I.G - http://cdn.myanimelist.net/images/anime/8/58681.jpg - xxxHOLiC: Rei - - - 1 - 22465 + 1 + 22465 + NHK - http://cdn.myanimelist.net/images/anime/7/58683.jpg - Kutsushita ga Daru Daru ni Nacchau Wake: Imadoki Youkai Zukan + http://cdn.myanimelist.net/images/anime/7/58683.jpg + Kutsushita ga Daru Daru ni Nacchau Wake: Imadoki Youkai Zukan - 1 - 22503 + 1 + 22503 + Studio Deen - http://cdn.myanimelist.net/images/anime/11/58721.jpg - Washimo + http://cdn.myanimelist.net/images/anime/11/58721.jpg + Washimo - 1 - 22547 + 1 + 22547 + 8293 Gonzo - http://cdn.myanimelist.net/images/anime/12/58815.jpg - Blade and Soul + http://cdn.myanimelist.net/images/anime/12/58815.jpg + Blade and Soul - 1 - 22693 + 1 + 22693 + TV Tokyo - http://cdn.myanimelist.net/images/anime/9/59069.jpg - Lady Jewelpet + http://cdn.myanimelist.net/images/anime/9/59069.jpg + Lady Jewelpet - 1 - 22733 - - http://cdn.myanimelist.net/images/anime/13/59129.jpg - Dragon Collection + 1 + 22733 + + + http://cdn.myanimelist.net/images/anime/13/59129.jpg + Dragon Collection - 1 - 22735 - - http://cdn.myanimelist.net/images/anime/12/59131.jpg - Oreca Battle + 1 + 22735 + + + http://cdn.myanimelist.net/images/anime/12/59131.jpg + Oreca Battle - 2 - 22759 + 2 + 22759 + J.C.Staff - http://cdn.myanimelist.net/images/anime/9/59195.jpg - Toaru Kagaku no Railgun S: Daiji na Koto wa Zenbu Sentou ni Osowatta + http://cdn.myanimelist.net/images/anime/9/59195.jpg + Toaru Kagaku no Railgun S: Daiji na Koto wa Zenbu Sentou ni Osowatta - 1 - 22777 + 1 + 22777 + Toei Animation - http://cdn.myanimelist.net/images/anime/10/59275.jpg - Dragon Ball Kai (2014) + http://cdn.myanimelist.net/images/anime/10/59275.jpg + Dragon Ball Kai (2014) - 1 - 22817 - - http://cdn.myanimelist.net/images/anime/10/59495.jpg - Kindaichi Shounen no Jikenbo Returns + 1 + 22817 + 8310 + + http://cdn.myanimelist.net/images/anime/10/59495.jpg + Kindaichi Shounen no Jikenbo Returns - 1 - 22821 - - http://cdn.myanimelist.net/images/anime/4/60101.jpg - Himitsu Kessha Taka no Tsume EX + 1 + 22821 + + + http://cdn.myanimelist.net/images/anime/4/60101.jpg + Himitsu Kessha Taka no Tsume EX - 1 - 22831 + 1 + 22831 + Toei Animation - http://cdn.myanimelist.net/images/anime/7/59301.jpg - Abarenbou Kishi!! Matsutarou + http://cdn.myanimelist.net/images/anime/7/59301.jpg + Abarenbou Kishi!! Matsutarou - 1 - 23107 + 1 + 23107 + Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/2/60033.jpg - GO-GO Tamagotchi! + http://cdn.myanimelist.net/images/anime/2/60033.jpg + GO-GO Tamagotchi! - 4 - 23115 + 4 + 23115 + NAZ, DIVE II Entertainment - http://cdn.myanimelist.net/images/anime/3/59905.jpg - Hamatora Saishuukai Chokuzen! Mao ga Okuru Soushuuhen Special: File-SP Jouhouya no Tokushu + http://cdn.myanimelist.net/images/anime/3/59905.jpg + Hamatora Saishuukai Chokuzen! Mao ga Okuru Soushuuhen Special: File-SP Jouhouya no Tokushu - 4 - 23117 + 4 + 23117 + Production I.G - http://cdn.myanimelist.net/images/anime/3/59901.jpg - Ghost in the Shell: Arise - Border: Less Project + http://cdn.myanimelist.net/images/anime/3/59901.jpg + Ghost in the Shell: Arise - Border: Less Project - 1 - 23133 + 1 + 23133 + Satelight, flying DOG - http://cdn.myanimelist.net/images/anime/6/60087.jpg - M3: Sono Kuroki Hagane + http://cdn.myanimelist.net/images/anime/6/60087.jpg + M3: Sono Kuroki Hagane - 1 - 23213 + 1 + 23213 + DLE - http://cdn.myanimelist.net/images/anime/11/60065.jpg - Kantoku Fuyuki Todoki + http://cdn.myanimelist.net/images/anime/11/60065.jpg + Kantoku Fuyuki Todoki - 2 - 23227 + 2 + 23227 + Doga Kobo, Toho Company, DAX Production, Mikakunin de Shinkoukei Production Committee - http://cdn.myanimelist.net/images/anime/7/60095.jpg - Mikakunin de Shinkoukei OVA + http://cdn.myanimelist.net/images/anime/7/60095.jpg + Mikakunin de Shinkoukei OVA - 1 - 23229 + 1 + 23229 + Kadokawa Shoten - http://cdn.myanimelist.net/images/anime/7/60097.jpg - Meshimase Lodoss-tou Senki: Sorette Oishii no? + http://cdn.myanimelist.net/images/anime/7/60097.jpg + Meshimase Lodoss-tou Senki: Sorette Oishii no? + + + 4 + 23237 + + + http://cdn.myanimelist.net/images/anime/13/60351.jpg + Chuunibyou demo Koi ga Shitai! Ren Specials \ No newline at end of file diff --git a/data/db/season/2014_winter.xml b/data/db/season/2014_winter.xml index aa7804533..245acd808 100644 --- a/data/db/season/2014_winter.xml +++ b/data/db/season/2014_winter.xml @@ -1,616 +1,735 @@ - + Winter 2014 - 1387974616 + 1387974616 - 2 - 18441 + 2 + 18441 + 7724 Brains Base - http://cdn.myanimelist.net/images/anime/2/52611.jpg - Blood Lad OVA + http://cdn.myanimelist.net/images/anime/2/52611.jpg + Blood Lad OVA - 4 - 21441 + 4 + 21441 + CoMix Wave - http://cdn.myanimelist.net/images/anime/8/56577.jpg - Taisei Kensetsu: Sri Lanka Kousokudouro + http://cdn.myanimelist.net/images/anime/8/56577.jpg + Taisei Kensetsu: Sri Lanka Kousokudouro - 5 - 21471 + 5 + 21471 + Kamikaze Douga - http://cdn.myanimelist.net/images/anime/4/56911.jpg - Mirai Koushi Harima SACLA + http://cdn.myanimelist.net/images/anime/4/56911.jpg + Mirai Koushi Harima SACLA - 2 - 19669 + 2 + 19669 + 7903 Production I.G - http://cdn.myanimelist.net/images/anime/12/55501.jpg - Kuroko no Basket OVA + http://cdn.myanimelist.net/images/anime/12/55501.jpg + Kuroko no Basket OVA - 4 - 21595 + 4 + 21595 + 8250 TV Tokyo, NAS - http://cdn.myanimelist.net/images/anime/3/56935.jpg - Yu-Gi-Oh! Zexal Second: Iza! Saishuu Kessen e!! Special + http://cdn.myanimelist.net/images/anime/3/56935.jpg + Yu-Gi-Oh! Zexal Second: Iza! Saishuu Kessen e!! Special - 2 - 17641 + 2 + 17641 + 7839 FUNimation Entertainment, AIC Plus+, Nippon Columbia - http://cdn.myanimelist.net/images/anime/6/56729.jpg - Date A Live OVA + http://cdn.myanimelist.net/images/anime/6/56729.jpg + Date A Live OVA - 1 - 21433 + 1 + 21433 + Nippon Animation - http://cdn.myanimelist.net/images/anime/13/56613.jpg - Chou Zenmairobo: Patrasche + http://cdn.myanimelist.net/images/anime/13/56613.jpg + Chou Zenmairobo: Patrasche - 3 - 18429 + 3 + 18429 + 7723 TMS Entertainment - http://cdn.myanimelist.net/images/anime/4/51663.jpg - Lupin III vs. Detective Conan: The Movie + http://cdn.myanimelist.net/images/anime/4/51663.jpg + Lupin III vs. Detective Conan: The Movie - 2 - 18397 + 2 + 18397 + 7694 Production I.G, Wit Studio - http://cdn.myanimelist.net/images/anime/4/56737.jpg - Shingeki no Kyojin OVA + http://cdn.myanimelist.net/images/anime/4/56737.jpg + Shingeki no Kyojin OVA - 4 - 21009 + 4 + 21009 + 8191 Arms, Movic, Earth Star Entertainment - http://cdn.myanimelist.net/images/anime/7/55775.jpg - Sekai de Ichiban Tsuyoku Naritai! Specials + http://cdn.myanimelist.net/images/anime/7/55775.jpg + Sekai de Ichiban Tsuyoku Naritai! Specials - 5 - 21587 + 5 + 21587 + 8205 Production I.G - http://cdn.myanimelist.net/images/anime/10/56893.jpg - Mou Hitotsu no Mirai wo. + http://cdn.myanimelist.net/images/anime/10/56893.jpg + Mou Hitotsu no Mirai wo. - 3 - 19587 + 3 + 19587 + Sunrise, Pony Canyon, Nippon Columbia - http://cdn.myanimelist.net/images/anime/10/52451.jpg - Kaiketsu Zorori: Mamoru ze! Kyouryuu no Tamago + http://cdn.myanimelist.net/images/anime/10/52451.jpg + Kaiketsu Zorori: Mamoru ze! Kyouryuu no Tamago - 4 - 20987 + 4 + 20987 + 8202 Tatsunoko Productions - http://cdn.myanimelist.net/images/anime/6/55779.jpg - Yozakura Quartet: Yoza-Quar! + http://cdn.myanimelist.net/images/anime/6/55779.jpg + Yozakura Quartet: Yoza-Quar! - 5 - 21335 - - http://cdn.myanimelist.net/images/anime/6/56319.jpg - Double Circle + 5 + 21335 + 8090 + Toshiba Entertainment + http://cdn.myanimelist.net/images/anime/6/56319.jpg + Double Circle - 2 - 20539 + 2 + 20539 + 8028 Yomiko Advertising, BeeWorks - http://cdn.myanimelist.net/images/anime/12/55085.jpg - Nameko-ke no Ichizoku + http://cdn.myanimelist.net/images/anime/12/55085.jpg + Nameko-ke no Ichizoku - 2 - 19159 + 2 + 19159 + 7820 Production I.G - http://cdn.myanimelist.net/images/anime/12/57145.jpg - Genshiken Nidaime OVA + http://cdn.myanimelist.net/images/anime/12/57145.jpg + Genshiken Nidaime OVA - 1 - 21325 + 1 + 21325 + 8145 AIC Plus+ - http://cdn.myanimelist.net/images/anime/4/56309.jpg - Pupipo! + http://cdn.myanimelist.net/images/anime/4/56309.jpg + Pupipo! - 1 - 19315 + 1 + 19315 + 7845 Studio Deen, Earth Star Entertainment, Pupa Production Committee - http://cdn.myanimelist.net/images/anime/2/54395.jpg - Pupa + http://cdn.myanimelist.net/images/anime/2/54395.jpg + Pupa - 4 - 21465 - - http://cdn.myanimelist.net/images/anime/9/56757.jpg - Kakashi Anbu Hen: Yami wo Ikiru Shinobu + 4 + 21465 + 8184 + Studio Pierrot + http://cdn.myanimelist.net/images/anime/9/56757.jpg + Kakashi Anbu Hen: Yami wo Ikiru Shinobu - 5 - 21641 - P.A. Works - http://cdn.myanimelist.net/images/anime/13/57001.jpg - Planet:Valkyrie - - - 4 - 19251 + 4 + 19251 + 7899 A-1 Pictures, Showgate - http://cdn.myanimelist.net/images/anime/9/56553.jpg - Uta no☆Prince-sama♪ Maji Love 2000% Special + http://cdn.myanimelist.net/images/anime/9/56553.jpg + Uta no☆Prince-sama♪ Maji Love 2000% Special - 4 - 21373 + 4 + 21373 + 8168 Media Factory, A.C.G.T. - http://cdn.myanimelist.net/images/anime/8/56413.jpg - Freezing Vibration Specials + http://cdn.myanimelist.net/images/anime/8/56413.jpg + Freezing Vibration Specials - 1 - 19855 + 1 + 19855 + 7771 VAP, Bridge, Earth Star Entertainment - http://cdn.myanimelist.net/images/anime/2/55989.jpg - Nobunagun + http://cdn.myanimelist.net/images/anime/2/55989.jpg + Nobunagun - 4 - 21415 + 4 + 21415 + Genco, Lerche - http://cdn.myanimelist.net/images/anime/5/56525.jpg - Machine-Doll wa Kizutsukanai Specials + http://cdn.myanimelist.net/images/anime/5/56525.jpg + Machine-Doll wa Kizutsukanai Specials - 1 - 21177 + 1 + 21177 + 8076 Satelight, Lantis, Media Factory - http://cdn.myanimelist.net/images/anime/3/55993.jpg - Nobunaga the Fool + http://cdn.myanimelist.net/images/anime/3/55993.jpg + Nobunaga the Fool - 3 - 19951 + 3 + 19951 + 7908 Madhouse - http://cdn.myanimelist.net/images/anime/7/55737.jpg - Hunter x Hunter: The Last Mission + http://cdn.myanimelist.net/images/anime/7/55737.jpg + Hunter x Hunter: The Last Mission - 3 - 15813 + 3 + 15813 + 7316 ufotable, Starchild Records, Tokuma Shoten - http://cdn.myanimelist.net/images/anime/5/53567.jpg - Majokko Shimai no Yoyo to Nene + http://cdn.myanimelist.net/images/anime/5/53567.jpg + Majokko Shimai no Yoyo to Nene - 1 - 21085 + 1 + 21085 + 8017 J.C. Staff, Lantis - http://cdn.myanimelist.net/images/anime/12/55693.jpg - Witch Craft Works + http://cdn.myanimelist.net/images/anime/12/55693.jpg + Witch Craft Works - 1 - 21437 + 1 + 21437 + 8139 Sunrise, Lantis - http://cdn.myanimelist.net/images/anime/12/57303.jpg - Buddy Complex + http://cdn.myanimelist.net/images/anime/12/57303.jpg + Buddy Complex - 2 - 19953 + 2 + 19953 + 7963 Encourage Films - http://cdn.myanimelist.net/images/anime/7/57269.jpg - Zetsumetsu Kigu Shoujo: Amazing Twins + http://cdn.myanimelist.net/images/anime/7/57269.jpg + Zetsumetsu Kigu Shoujo: Amazing Twins - 4 - 20021 + 4 + 20021 + 7914 A-1 Pictures, Aniplex of America - http://cdn.myanimelist.net/images/anime/12/56299.jpg - Sword Art Online: Extra Edition + http://cdn.myanimelist.net/images/anime/12/56299.jpg + Sword Art Online: Extra Edition - 4 - 19653 + 4 + 19653 + 7874 Bandai Visual, Kinema Citrus, Nitroplus, Bushiroad Inc. - http://cdn.myanimelist.net/images/anime/11/56827.jpg - Neppuu Kairiku Bushi Road + http://cdn.myanimelist.net/images/anime/11/56827.jpg + Neppuu Kairiku Bushi Road - 3 - 19489 + 3 + 19489 + 7862 Trigger - http://cdn.myanimelist.net/images/anime/5/52295.jpg - Little Witch Academia 2 + http://cdn.myanimelist.net/images/anime/5/52295.jpg + Little Witch Academia 2 - 1 - 20533 + 1 + 20533 + 8030 Pony Canyon - http://cdn.myanimelist.net/images/anime/6/56797.jpg - Z/X: Ignition + http://cdn.myanimelist.net/images/anime/6/56797.jpg + Z/X: Ignition - 5 - 14751 - - http://cdn.myanimelist.net/images/anime/9/40091.jpg - Bishoujo Senshi Sailor Moon (2014) + 5 + 14751 + 7163 + Toei Animation, Starchild Records + http://cdn.myanimelist.net/images/anime/9/40091.jpg + Bishoujo Senshi Sailor Moon (2014) - 4 - 21329 + 4 + 21329 + 8112 Artland, Aniplex - http://cdn.myanimelist.net/images/anime/3/56315.jpg - Mushishi Special: Hihamukage + http://cdn.myanimelist.net/images/anime/3/56315.jpg + Mushishi Special: Hihamukage - 1 - 19799 + 1 + 19799 + 7875 Toei Animation - http://cdn.myanimelist.net/images/anime/4/53141.jpg - Robot Girls Z + http://cdn.myanimelist.net/images/anime/4/53141.jpg + Robot Girls Z - 1 - 20057 + 1 + 20057 + 7910 Bones, Bandai Visual, FUNimation Entertainment, flying DOG - http://cdn.myanimelist.net/images/anime/4/56611.jpg - Space☆Dandy + http://cdn.myanimelist.net/images/anime/4/56611.jpg + Space☆Dandy - 1 - 20847 + 1 + 20847 + 8061 Starchild Records - http://cdn.myanimelist.net/images/anime/9/56941.jpg - Seitokai Yakuindomo* + http://cdn.myanimelist.net/images/anime/9/56941.jpg + Seitokai Yakuindomo* - 1 - 17777 + 1 + 17777 + 8060 Project No.9 - http://cdn.myanimelist.net/images/anime/3/56589.jpg - Saikin, Imouto no Yousu ga Chotto Okashiinda ga. + http://cdn.myanimelist.net/images/anime/3/56589.jpg + Saikin, Imouto no Yousu ga Chotto Okashiinda ga. - 1 - 19067 + 1 + 19067 + 7795 Xebec, Oriental Light and Magic, Bushiroad Inc. - http://cdn.myanimelist.net/images/anime/2/51125.jpg - Future Card Buddyfight + http://cdn.myanimelist.net/images/anime/2/51125.jpg + Future Card Buddyfight - 2 - 20977 + 2 + 20977 + 8198 Shin-Ei Animation - http://cdn.myanimelist.net/images/anime/10/55789.jpg - Tonari no Seki-kun OVA + http://cdn.myanimelist.net/images/anime/10/55789.jpg + Tonari no Seki-kun OVA - 1 - 21427 + 1 + 21427 + 8058 DAX Production - http://cdn.myanimelist.net/images/anime/10/56549.jpg - Minna Atsumare! Falcom Gakuen + http://cdn.myanimelist.net/images/anime/10/56549.jpg + Minna Atsumare! Falcom Gakuen - 1 - 21507 - Bones - http://cdn.myanimelist.net/images/anime/10/56815.jpg - Soul Eater Not! - - - 1 - 20555 + 1 + 20555 + 8031 Genco, Pony Canyon, Half H.P Studio, White Fox, Nitroplus - http://cdn.myanimelist.net/images/anime/3/56987.jpg - Super Sonico The Animation + http://cdn.myanimelist.net/images/anime/3/56987.jpg + Super Sonico The Animation - 1 - 19117 + 1 + 19117 + 7770 Bandai Visual, TMS Entertainment - http://cdn.myanimelist.net/images/anime/2/56939.jpg - Toaru Hikuushi e no Koiuta + http://cdn.myanimelist.net/images/anime/2/56939.jpg + Toaru Hikuushi e no Koiuta - 1 - 18139 + 1 + 18139 + 7761 Starchild Records, Media Factory, Shin-Ei Animation - http://cdn.myanimelist.net/images/anime/9/55489.jpg - Tonari no Seki-kun + http://cdn.myanimelist.net/images/anime/9/55489.jpg + Tonari no Seki-kun - 1 - 16123 + 1 + 16123 + 7375 TV Tokyo, Square Enix, Lantis, Pony Canyon, Studio Gokumi, Saki Zenkoku-hen Production Committee - http://cdn.myanimelist.net/images/anime/5/56681.jpg - Saki: Zenkoku-hen + http://cdn.myanimelist.net/images/anime/12/57979.jpg + Saki: Zenkoku-hen - 1 - 20031 + 1 + 20031 + 8001 Media Factory, Brains Base - http://cdn.myanimelist.net/images/anime/2/53407.jpg - D-Frag! + http://cdn.myanimelist.net/images/anime/2/53407.jpg + D-Frag! - 1 - 20267 + 1 + 20267 + 7979 TV Tokyo, SANZIGEN - http://cdn.myanimelist.net/images/anime/5/56609.jpg - Wooser no Sono Higurashi 2 Kakusei-hen + http://cdn.myanimelist.net/images/anime/5/56609.jpg + Wooser no Sono Higurashi: Kakusei-hen - 4 - 20909 + 4 + 20909 + 8088 Kyoto Animation, Lantis, Pony Canyon - http://cdn.myanimelist.net/images/anime/9/55781.jpg - Kyoukai no Kanata: Mini Theater + http://cdn.myanimelist.net/images/anime/9/55781.jpg + Kyoukai no Kanata: Mini Theater - 1 - 20689 + 1 + 20689 + 8057 NAZ, DIVE II Entertainment - http://cdn.myanimelist.net/images/anime/10/55181.jpg - Hamatora The Animation + http://cdn.myanimelist.net/images/anime/10/55181.jpg + Hamatora The Animation - 1 - 19157 + 1 + 19157 + 8010 Oriental Light and Magic - http://cdn.myanimelist.net/images/anime/11/56625.jpg - Youkai Watch + http://cdn.myanimelist.net/images/anime/11/56625.jpg + Youkai Watch - 1 - 20541 + 1 + 20541 + 8027 Doga Kobo, Toho Company, DAX Production, Mikakunin de Shinkoukei Production Committee - http://cdn.myanimelist.net/images/anime/6/55261.jpg - Mikakunin de Shinkoukei + http://cdn.myanimelist.net/images/anime/6/55261.jpg + Mikakunin de Shinkoukei - 1 - 20931 + 1 + 20931 + 8083 Takeshobo, C2C - http://cdn.myanimelist.net/images/anime/3/56415.jpg - Onee-chan ga Kita + http://cdn.myanimelist.net/images/anime/3/56415.jpg + Onee-chan ga Kita - 1 - 21447 + 1 + 21447 + Nippon Animation - http://cdn.myanimelist.net/images/anime/5/56615.jpg - Meitantei Rascal + http://cdn.myanimelist.net/images/anime/5/56615.jpg + Meitantei Rascal - 1 - 18671 + 1 + 18671 + 7705 Kyoto Animation - http://cdn.myanimelist.net/images/anime/7/56643.jpg - Chuunibyou demo Koi ga Shitai! Ren + http://cdn.myanimelist.net/images/anime/7/56643.jpg + Chuunibyou demo Koi ga Shitai! Ren - 1 - 21267 + 1 + 21267 + 8092 Sega - http://cdn.myanimelist.net/images/anime/10/56433.jpg - Go! Go! 575 + http://cdn.myanimelist.net/images/anime/10/56433.jpg + Go! Go! 575 - 1 - 19769 + 1 + 19769 + 7879 Madhouse, Media Factory, flying DOG - http://cdn.myanimelist.net/images/anime/5/56303.jpg - Mahou Sensou + http://cdn.myanimelist.net/images/anime/5/56303.jpg + Mahou Sensou - 1 - 21067 + 1 + 21067 + 8099 Seven, Dream Creation - http://cdn.myanimelist.net/images/anime/13/56487.jpg - Strange+ + http://cdn.myanimelist.net/images/anime/6/57481.jpg + Strange+ - 1 - 20047 + 1 + 20047 + 7978 Studio Deen, Pony Canyon, TBS - http://cdn.myanimelist.net/images/anime/2/56189.jpg - Sakura Trick + http://cdn.myanimelist.net/images/anime/2/56189.jpg + Sakura Trick - 1 - 19363 + 1 + 19363 + 7860 Aniplex, A-1 Pictures, Fuji TV - http://cdn.myanimelist.net/images/anime/3/51915.jpg - Gin no Saji 2nd Season + http://cdn.myanimelist.net/images/anime/3/51915.jpg + Gin no Saji 2nd Season - 3 - 21189 + 3 + 21189 + 8200 Tatsunoko Productions, Ordet - http://cdn.myanimelist.net/images/anime/13/55953.jpg - Wake Up, Girls! Shichinin no Idol + http://cdn.myanimelist.net/images/anime/13/55953.jpg + Wake Up, Girls! Shichinin no Idol - 1 - 20431 + 1 + 20431 + 8009 Starchild Records, Wit Studio - http://cdn.myanimelist.net/images/anime/9/55913.jpg - Hoozuki no Reitetsu + http://cdn.myanimelist.net/images/anime/9/55913.jpg + Hoozuki no Reitetsu - 1 - 19023 + 1 + 19023 + 7774 Tatsunoko Productions, Ordet - http://cdn.myanimelist.net/images/anime/3/52777.jpg - Wake Up, Girls! + http://cdn.myanimelist.net/images/anime/3/52777.jpg + Wake Up, Girls! - 1 - 18095 + 1 + 18095 + 7782 Frontier Works, Silver Link - http://cdn.myanimelist.net/images/anime/8/53139.jpg - Nourin + http://cdn.myanimelist.net/images/anime/8/53139.jpg + Nourin - 1 - 18897 + 1 + 18897 + 7821 Aniplex, Shaft, Aniplex of America - http://cdn.myanimelist.net/images/anime/12/54337.jpg - Nisekoi + http://cdn.myanimelist.net/images/anime/12/54337.jpg + Nisekoi - 1 - 20973 + 1 + 20973 + 8082 Aniplex, A-1 Pictures, flying DOG, Aniplex of America - http://cdn.myanimelist.net/images/anime/2/56133.jpg - Sekai Seifuku: Bouryaku no Zvezda + http://cdn.myanimelist.net/images/anime/2/56133.jpg + Sekai Seifuku: Bouryaku no Zvezda - 1 - 20053 + 1 + 20053 + 7898 Arms, Pony Canyon - http://cdn.myanimelist.net/images/anime/13/53729.jpg - Wizard Barristers: Benmashi Cecil + http://cdn.myanimelist.net/images/anime/13/53729.jpg + Wizard Barristers: Benmashi Cecil - 1 - 15565 + 1 + 15565 + 7275 Xebec - http://cdn.myanimelist.net/images/anime/5/49971.jpg - Maken-Ki! Two + http://cdn.myanimelist.net/images/anime/5/49971.jpg + Maken-Ki! Two - 1 - 20457 + 1 + 20457 + 7988 DAX Production, flying DOG, Production IMS - http://cdn.myanimelist.net/images/anime/2/54903.jpg - Inari, Konkon, Koi Iroha. + http://cdn.myanimelist.net/images/anime/2/54903.jpg + Inari, Konkon, Koi Iroha. - 2 - 20649 + 2 + 20649 + 8185 Gonzo - http://cdn.myanimelist.net/images/anime/4/55157.jpg - Kimi no Iru Machi OVA + http://cdn.myanimelist.net/images/anime/4/55157.jpg + Kimi no Iru Machi OVA - 4 - 20423 + 4 + 20423 + 7989 Madhouse - http://cdn.myanimelist.net/images/anime/6/54227.jpg - Kamisama no Inai Nichiyoubi Special + http://cdn.myanimelist.net/images/anime/6/54227.jpg + Kamisama no Inai Nichiyoubi Special - 3 - 17437 + 3 + 17437 + 7824 A-1 Pictures - http://cdn.myanimelist.net/images/anime/4/56279.jpg - The iDOLM@STER Movie: Kagayaki no Mukougawa e! + http://cdn.myanimelist.net/images/anime/4/56279.jpg + The iDOLM@STER Movie: Kagayaki no Mukougawa e! - 2 - 21599 + 2 + 21599 + Avex Entertainment, Genco - http://cdn.myanimelist.net/images/anime/11/56899.jpg - Fight Ippatsu! Juuden-chan!! OVA - - - 2 - 18851 - Silver Link - http://cdn.myanimelist.net/images/anime/11/50659.jpg - Fate/kaleid liner Prisma☆Illya (2014) + http://cdn.myanimelist.net/images/anime/11/56899.jpg + Fight Ippatsu! Juuden-chan!! OVA - 4 - 20517 + 4 + 20517 + 8019 J.C. Staff - http://cdn.myanimelist.net/images/anime/3/54535.jpg - Little Busters!: EX + http://cdn.myanimelist.net/images/anime/3/54535.jpg + Little Busters!: EX - 1 - 21407 + 1 + 21407 + 8269 Toei Animation - http://cdn.myanimelist.net/images/anime/6/56499.jpg - Happiness Charge Precure! + http://cdn.myanimelist.net/images/anime/13/57375.jpg + Happiness Charge Precure! - 2 - 20843 + 2 + 20843 + 8081 Satelight, Starchild Records - http://cdn.myanimelist.net/images/anime/9/55131.jpg - Senki Zesshou Symphogear G: In the Distance, That Day, When the Star Became Music... OVA + http://cdn.myanimelist.net/images/anime/9/55131.jpg + Senki Zesshou Symphogear G: In the Distance, That Day, When the Star Became Music... OVA - 2 - 20651 + 2 + 20651 + 8054 Aniplex, Brains Base, NAS - http://cdn.myanimelist.net/images/anime/2/54783.jpg - Natsume Yuujinchou: Itsuka Yuki no Hi ni + http://cdn.myanimelist.net/images/anime/2/54783.jpg + Natsume Yuujinchou: Itsuka Yuki no Hi ni - 3 - 20969 + 3 + 20969 + 8178 Toei Animation, Tezuka Productions - http://cdn.myanimelist.net/images/anime/9/55453.jpg - Buddha 2: Tezuka Osamu no Buddha: Owarinaki Tabi + http://cdn.myanimelist.net/images/anime/9/55453.jpg + Buddha 2: Tezuka Osamu no Buddha: Owarinaki Tabi - 3 - 12017 + 3 + 12017 + 6644 Sunrise, Viz Media - http://cdn.myanimelist.net/images/anime/9/56915.jpg - Tiger & Bunny Movie 2: The Rising + http://cdn.myanimelist.net/images/anime/9/56915.jpg + Tiger & Bunny Movie 2: The Rising - 2 - 20767 + 2 + 20767 + 8152 Bones, Avex Entertainment, Ai Addiction - http://cdn.myanimelist.net/images/anime/10/54931.jpg - Noragami OVA + http://cdn.myanimelist.net/images/anime/10/54931.jpg + Noragami OVA - 3 - 19115 + 3 + 19115 + 7801 Production I.G, Warner Bros. - http://cdn.myanimelist.net/images/anime/5/56297.jpg - Giovanni no Shima + http://cdn.myanimelist.net/images/anime/5/56297.jpg + Giovanni no Shima - 3 - 14817 + 3 + 14817 + 7170 Satelight, Starchild Records - http://cdn.myanimelist.net/images/anime/6/54181.jpg - Mouretsu Pirates: Abyss of Hyperspace + http://cdn.myanimelist.net/images/anime/6/54181.jpg + Mouretsu Pirates: Abyss of Hyperspace - 4 - 21075 - - http://cdn.myanimelist.net/images/anime/11/55751.jpg - Hetalia: The Beautiful World Extra Disc + 4 + 21075 + 8249 + Studio Deen + http://cdn.myanimelist.net/images/anime/2/58297.jpg + Hetalia: The Beautiful World Extra Disc - 6 - 21103 + 6 + 21103 + 8101 P.A. Works - http://cdn.myanimelist.net/images/anime/13/55745.jpg - Utopia + http://cdn.myanimelist.net/images/anime/13/55745.jpg + Utopia + + + 1 + 21851 + 8172 + Tesagure! Production Committee + http://cdn.myanimelist.net/images/anime/7/58483.jpg + Tesagure! Bukatsumono Encore + + + x + 21703 + + TV Tokyo, Oriental Light and Magic + http://cdn.myanimelist.net/images/anime/13/57141.jpg + Pokemon XY: New Year Special + + + 4 + 22335 + 8289 + MAPPA, Earth Star Entertainment + http://cdn.myanimelist.net/images/anime/7/58477.jpg + Teekyuu 3 Specials + + + 5 + 22377 + 8290 + Graphinica, Namco Bandai Games + http://cdn.myanimelist.net/images/anime/8/58783.jpg + Wonder Momo + + + 4 + 22673 + + Production I.G + http://cdn.myanimelist.net/images/anime/13/59027.jpg + Kuroko no Basket 2nd Season NG-shuu + + + 4 + 22745 + + + http://cdn.myanimelist.net/images/anime/3/59141.jpg + Brothers Conflict Special + + + 4 + 22859 + + Kyoto Animation + http://cdn.myanimelist.net/images/anime/8/59343.jpg + Takanashi Rikka Kai: Chuunibyou demo Koi ga Shitai! Movie Lite \ No newline at end of file diff --git a/data/media.xml b/data/media.xml index 9927fc8bd..21a193ffc 100644 --- a/data/media.xml +++ b/data/media.xml @@ -61,17 +61,6 @@ GOM Player [Built-in subtitle] - - - JetAudio - 1 - 1 - 3 - COWON Jet-Audio MainWnd Class - JetAudio.exe - %ProgramFiles%\JetAudio\ - %ProgramW6432%\JetAudio\ - Kantaris Media Player @@ -209,8 +198,10 @@ PotPlayer64.exe PotPlayerMini.exe PotPlayerMini64.exe + sumire.exe %ProgramFiles%\Daum\PotPlayer\ %ProgramW6432%\Daum\PotPlayer\ + %ProgramFiles%\LAV Filters\x86\PotPlayer\ @@ -236,9 +227,9 @@ %ProgramFiles%\Mirillis\Splash Lite\ %ProgramW6432%\Mirillis\Splash Lite\ - + - Shooter Player + SPlayer 1 1 0 diff --git a/data/test/recognition.xml b/data/test/recognition.xml index 8ba39b27b..46bf11814 100644 --- a/data/test/recognition.xml +++ b/data/test/recognition.xml @@ -658,4 +658,51 @@ 01 Ro-Kyu-Bu! SS + + [Raizel] Persona 4 The Animation Episode 13 - A Stormy Summer Vacation Part 1 [BD_1080p_Dual_Audio_FLAC_Hi10p][8A45634B].mkv + + 8A45634B + BD + MKV + Raizel + A Stormy Summer Vacation Part 1 + 13 + 1080p + Persona 4 The Animation + + + + [Hien] Kotoura-san - Special Short Anime 'Haruka's Room' - 01 [BD 1080p H.264 10-bit AAC][6B6BE015].mkv + + 6B6BE015 + BD + MKV + Hien + 01 + 1080p + Kotoura-san - Special Short Anime 'Haruka's Room' + + + + [R-R] Diebuster.EP1 (720p.Hi10p.AC3) [82E36A36].mkv + + 82E36A36 + MKV + R-R + 1 + 720p + Diebuster + + + + Aim_For_The_Top!_Gunbuster-ep1.BD(H264.FLAC.10bit)[KAA][69ECCDCF].mkv + + 69ECCDCF + BD + MKV + KAA + 1 + Aim For The Top! Gunbuster + + \ No newline at end of file diff --git a/data/theme/Default/24px/myanimelist.png b/data/theme/Default/24px/myanimelist.png deleted file mode 100644 index 835002dbc..000000000 Binary files a/data/theme/Default/24px/myanimelist.png and /dev/null differ diff --git a/data/theme/Default/Theme.xml b/data/theme/Default/Theme.xml index f3c477dfc..4cca04293 100644 --- a/data/theme/Default/Theme.xml +++ b/data/theme/Default/Theme.xml @@ -39,7 +39,6 @@ - diff --git a/dde.cpp b/dde.cpp deleted file mode 100644 index 715f5e206..000000000 --- a/dde.cpp +++ /dev/null @@ -1,173 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "dde.h" - -#include "string.h" - -// ============================================================================= - -DynamicDataExchange::DynamicDataExchange() - : is_unicode_(FALSE), - instance_(0), - conversation_(NULL) { -} - -DynamicDataExchange::~DynamicDataExchange() { - Disconnect(); - UnInitialize(); -} - -BOOL DynamicDataExchange::ClientTransaction(const wstring& item, const wstring& data, wstring* output, UINT wType) { - HDDEDATA hData = NULL; - HSZ hszItem = NULL; - DWORD dwResult = 0; - if (is_unicode_) { - if (wType != XTYP_EXECUTE) { - hszItem = ::DdeCreateStringHandleW(instance_, item.c_str(), CP_WINUNICODE); - } - hData = ::DdeClientTransaction((LPBYTE)data.data(), (data.size() + 1) * sizeof(WCHAR), conversation_, - hszItem, CF_UNICODETEXT, wType, 3000, &dwResult); - } else { - string item_ansi = ToANSI(item), data_ansi = ToANSI(data); - if (wType != XTYP_EXECUTE) { - hszItem = ::DdeCreateStringHandleA(instance_, item_ansi.c_str(), CP_WINANSI); - } - hData = ::DdeClientTransaction((LPBYTE)data_ansi.data(), data_ansi.size() + 1, conversation_, - hszItem, CF_TEXT, wType, 3000, &dwResult); - } - ::DdeFreeStringHandle(instance_, hszItem); - if (output) { - char szResult[255]; - ::DdeGetData(hData, (unsigned char*)szResult, 255, 0); - output->assign(ToUTF8(szResult)); - } - return hData != 0; -} - -BOOL DynamicDataExchange::Connect(const wstring& service, const wstring& topic) { - if (instance_) { - HSZ hszService = NULL, hszTopic = NULL; - if (is_unicode_) { - hszService = ::DdeCreateStringHandleW(instance_, service.c_str(), CP_WINUNICODE); - hszTopic = ::DdeCreateStringHandleW(instance_, topic.c_str(), CP_WINUNICODE); - } else { - hszService = ::DdeCreateStringHandleA(instance_, ToANSI(service), CP_WINANSI); - hszTopic = ::DdeCreateStringHandleA(instance_, ToANSI(topic), CP_WINANSI); - } - conversation_ = ::DdeConnect(instance_, hszService, hszTopic, NULL); - ::DdeFreeStringHandle(instance_, hszService); - ::DdeFreeStringHandle(instance_, hszTopic); - return conversation_ != NULL; - } else { - return FALSE; - } -} - -void DynamicDataExchange::Disconnect() { - if (conversation_) { - ::DdeDisconnect(conversation_); - conversation_ = NULL; - } -} - -BOOL DynamicDataExchange::Initialize(DWORD afCmd, BOOL unicode) { - is_unicode_ = unicode; - if (is_unicode_) { - ::DdeInitializeW(&instance_, DdeCallback, afCmd, 0); - } else { - ::DdeInitializeA(&instance_, DdeCallback, afCmd, 0); - } - return instance_ != 0; -} - -BOOL DynamicDataExchange::IsAvailable() { - return instance_ != 0; -} - -BOOL DynamicDataExchange::NameService(const wstring& service, UINT afCmd) { - HSZ hszService = NULL; - if (is_unicode_) { - hszService = ::DdeCreateStringHandleW(instance_, service.c_str(), CP_WINUNICODE); - } else { - hszService = ::DdeCreateStringHandleA(instance_, ToANSI(service), CP_WINANSI); - } - HDDEDATA result = ::DdeNameService(instance_, hszService, 0, afCmd); - ::DdeFreeStringHandle(instance_, hszService); - return result != 0; -} - -void DynamicDataExchange::UnInitialize() { - if (instance_) { - ::DdeUninitialize(instance_); - instance_ = 0; - } -} - -// ============================================================================= - -HDDEDATA CALLBACK DynamicDataExchange::DdeCallback(UINT uType, UINT uFmt, HCONV hconv, HSZ hsz1, HSZ hsz2, HDDEDATA hdata, DWORD dwData1, DWORD dwData2) { - DWORD cb = 0; - LPVOID lpData = NULL; - char sz1[256] = {'\0'}, sz2[256] = {'\0'}; - //DdeQueryStringA(instance_, hsz1, sz1, 256, CP_WINANSI); - //DdeQueryStringA(instance_, hsz2, sz2, 256, CP_WINANSI); - - switch (uType) { - case XTYP_CONNECT: { - OutputDebugStringA("[CONNECT]\n"); - //BOOL result = OnConnect(); - return reinterpret_cast(TRUE); - } - - case XTYP_POKE: { - if (hdata) lpData = DdeAccessData(hdata, &cb); -#ifdef _DEBUG - string str = "[POKE]"; - str += "Topic: " + *sz1; - str += " - Item: " + *sz2; - if (lpData) str += " - Data: "; str += (LPCSTR)lpData; - str += "\n"; - OutputDebugStringA(str.c_str()); -#endif - //OnPoke(); - if (hdata) DdeUnaccessData(hdata); - return reinterpret_cast(DDE_FACK); - } - - case XTYP_REQUEST: { - // TODO: Call DdeCreateDataHandle(); -#ifdef _DEBUG - string str = "[REQUEST] "; - str += "Topic: "; str += sz1; - str += " - Item: "; str += sz2; - str += "\n"; - OutputDebugStringA(str.c_str()); -#endif - //OnRequest(); - break; - } - - default: - break; - } - - return reinterpret_cast(0); -} \ No newline at end of file diff --git a/debug.cpp b/debug.cpp deleted file mode 100644 index 0897cdb03..000000000 --- a/debug.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "debug.h" - -#include "anime_db.h" -#include "common.h" -#include "myanimelist.h" -#include "string.h" - -#include "dlg/dlg_main.h" - -namespace debug { - -// ============================================================================= - -Tester::Tester() - : frequency_(0.0), value_(0) { -} - -void Tester::Start() { - LARGE_INTEGER li; - - if (frequency_ == 0.0) { - ::QueryPerformanceFrequency(&li); - frequency_ = double(li.QuadPart) / 1000.0; - } - - ::QueryPerformanceCounter(&li); - value_ = li.QuadPart; -} - -void Tester::End(wstring str, bool display_result) { - LARGE_INTEGER li; - - ::QueryPerformanceCounter(&li); - double value = double(li.QuadPart - value_) / frequency_; - - if (display_result) { - str = ToWstr(value, 2) + L"ms | Text: [" + str + L"]"; - MainDialog.SetText(str); - } -} - -// ============================================================================= - -void PrintBbcodeUrl(const anime::Item& anime_item) { - Print(L"[url=http://myanimelist.net/anime/" + ToWstr(anime_item.GetId()) + L"/]" + - anime_item.GetTitle() + L"[/url]\n"); -} - -void CheckDoubleSpace() { - for (auto item = AnimeDatabase.items.begin(); item != AnimeDatabase.items.end(); ++item) { - if (InStr(item->second.GetTitle(), L" ") > -1 || - InStr(Join(item->second.GetSynonyms(), L"; "), L" ") > -1) { - PrintBbcodeUrl(item->second); - } - } -} - -void CheckInvalidDates() { - for (auto item = AnimeDatabase.items.begin(); item != AnimeDatabase.items.end(); ++item) { - if (item->second.GetAiringStatus(true) != item->second.GetAiringStatus(false)) { - PrintBbcodeUrl(item->second); - } - } -} - -void CheckInvalidEpisodes() { - for (auto item = AnimeDatabase.items.begin(); item != AnimeDatabase.items.end(); ++item) { - if (item->second.GetEpisodeCount() < 0 || - item->second.GetEpisodeCount() > 500 || - (item->second.IsInList() && item->second.GetMyLastWatchedEpisode() > item->second.GetEpisodeCount())) { - PrintBbcodeUrl(item->second); - } - } -} - -void CheckSynonyms() { - for (auto item = AnimeDatabase.items.begin(); item != AnimeDatabase.items.end(); ++item) { - for (auto synonym = item->second.GetSynonyms().begin(); synonym != item->second.GetSynonyms().end(); ++synonym) { - if (InStr(*synonym, L";", 0, true) > -1) { - PrintBbcodeUrl(item->second); - break; - } - } - } -} - -// ============================================================================= - -void Print(wstring text) { -#ifdef _DEBUG - ::OutputDebugString(text.c_str()); -#else - UNREFERENCED_PARAMETER(text); -#endif -} - -void Test() { - // Define variables - wstring str; - - // Start ticking - Tester test; - test.Start(); - - for (int i = 0; i < 10000; i++) { - // Do some tests here - // ___ - // {o,o} - // |)__) - // --"-"-- - // O RLY? - } - - // Debugging MAL database - //CheckDoubleSpace(); - //CheckInvalidDates(); - //CheckInvalidEpisodes(); - //CheckSynonyms(); - - // Debugging recognition engine - //ExecuteAction(L"RecognitionTest"); - - // Show result - test.End(str, 0); -} - -} // namespace debug \ No newline at end of file diff --git a/third_party/base64/base64.cpp b/deps/src/base64/base64.cpp similarity index 95% rename from third_party/base64/base64.cpp rename to deps/src/base64/base64.cpp index 23461f7fc..b38ce64af 100644 --- a/third_party/base64/base64.cpp +++ b/deps/src/base64/base64.cpp @@ -1,267 +1,267 @@ -/* -** base64.cpp: implementation of the Base64Coder class. -** http://support.microsoft.com/kb/191239 -*/ - -#include "base64.h" - -static char Base64Digits[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -BOOL Base64Coder::m_Init = FALSE; -char Base64Coder::m_DecodeTable[256]; - -#ifndef PAGESIZE -#define PAGESIZE 4096 -#endif - -#ifndef ROUNDTOPAGE -#define ROUNDTOPAGE(a) (((a/4096)+1)*4096) -#endif - -// ============================================================================= - -Base64Coder::Base64Coder() : - m_pDBuffer(NULL), m_pEBuffer(NULL), - m_nDBufLen(0), m_nEBufLen(0) -{ -} - -Base64Coder::~Base64Coder() { - if (m_pDBuffer != NULL) delete [] m_pDBuffer; - if (m_pEBuffer != NULL) delete [] m_pEBuffer; -} - -// ============================================================================= - -LPCSTR Base64Coder::DecodedMessage() const { - return (LPCSTR)m_pDBuffer; -} - -LPCSTR Base64Coder::EncodedMessage() const { - return (LPCSTR)m_pEBuffer; -} - -void Base64Coder::AllocEncode(DWORD nSize) { - if (m_nEBufLen < nSize) { - if (m_pEBuffer != NULL) delete [] m_pEBuffer; - m_nEBufLen = ROUNDTOPAGE(nSize); - m_pEBuffer = new BYTE[m_nEBufLen]; - } - ::ZeroMemory(m_pEBuffer, m_nEBufLen); - m_nEDataLen = 0; -} - -void Base64Coder::AllocDecode(DWORD nSize) { - if (m_nDBufLen < nSize) { - if (m_pDBuffer != NULL) delete [] m_pDBuffer; - m_nDBufLen = ROUNDTOPAGE(nSize); - m_pDBuffer = new BYTE[m_nDBufLen]; - } - ::ZeroMemory(m_pDBuffer, m_nDBufLen); - m_nDDataLen = 0; -} - -void Base64Coder::SetEncodeBuffer(const PBYTE pBuffer, DWORD nBufLen) { - DWORD i = 0; - AllocEncode(nBufLen); - while (i < nBufLen) { - if (!_IsBadMimeChar(pBuffer[i])) { - m_pEBuffer[m_nEDataLen] = pBuffer[i]; - m_nEDataLen++; - } - i++; - } -} - -void Base64Coder::SetDecodeBuffer(const PBYTE pBuffer, DWORD nBufLen) { - AllocDecode(nBufLen); - ::CopyMemory(m_pDBuffer, pBuffer, nBufLen); - m_nDDataLen = nBufLen; -} - -void Base64Coder::Encode(const PBYTE pBuffer, DWORD nBufLen) { - SetDecodeBuffer(pBuffer, nBufLen); - AllocEncode(nBufLen * 2); - - TempBucket Raw; - DWORD nIndex = 0; - - while ((nIndex + 3) <= nBufLen) { - Raw.Clear(); - ::CopyMemory(&Raw, m_pDBuffer + nIndex, 3); - Raw.nSize = 3; - _EncodeToBuffer(Raw, m_pEBuffer + m_nEDataLen); - nIndex += 3; - m_nEDataLen += 4; - } - - if (nBufLen > nIndex) { - Raw.Clear(); - Raw.nSize = (BYTE)(nBufLen - nIndex); - ::CopyMemory(&Raw, m_pDBuffer + nIndex, nBufLen - nIndex); - _EncodeToBuffer(Raw, m_pEBuffer + m_nEDataLen); - m_nEDataLen += 4; - } -} - -void Base64Coder::Encode(LPCSTR szMessage) { - if (szMessage != NULL) { - Base64Coder::Encode((const PBYTE)szMessage, strlen(szMessage)); - } -} - -void Base64Coder::Decode(const PBYTE pBuffer, DWORD dwBufLen) { - if (!Base64Coder::m_Init) { - _Init(); - } - - SetEncodeBuffer(pBuffer, dwBufLen); - AllocDecode(dwBufLen); - - TempBucket Raw; - DWORD nIndex = 0; - - while ((nIndex + 4) <= m_nEDataLen) { - Raw.Clear(); - Raw.nData[0] = Base64Coder::m_DecodeTable[m_pEBuffer[nIndex]]; - Raw.nData[1] = Base64Coder::m_DecodeTable[m_pEBuffer[nIndex + 1]]; - Raw.nData[2] = Base64Coder::m_DecodeTable[m_pEBuffer[nIndex + 2]]; - Raw.nData[3] = Base64Coder::m_DecodeTable[m_pEBuffer[nIndex + 3]]; - - if (Raw.nData[2] == 255) Raw.nData[2] = 0; - if (Raw.nData[3] == 255) Raw.nData[3] = 0; - - Raw.nSize = 4; - _DecodeToBuffer(Raw, m_pDBuffer + m_nDDataLen); - nIndex += 4; - m_nDDataLen += 3; - } - - if (nIndex < m_nEDataLen) { - Raw.Clear(); - for (DWORD i = nIndex; i < m_nEDataLen; i++) { - Raw.nData[i - nIndex] = Base64Coder::m_DecodeTable[m_pEBuffer[i]]; - Raw.nSize++; - if (Raw.nData[i - nIndex] == 255) Raw.nData[i - nIndex] = 0; - } - _DecodeToBuffer(Raw, m_pDBuffer + m_nDDataLen); - m_nDDataLen += (m_nEDataLen - nIndex); - } -} - -void Base64Coder::Decode(LPCSTR szMessage) { - if (szMessage != NULL) { - Base64Coder::Decode((const PBYTE)szMessage, strlen(szMessage)); - } -} - -// ============================================================================= - -DWORD Base64Coder::_DecodeToBuffer(const TempBucket& Decode, PBYTE pBuffer) { - TempBucket Data; - DWORD nCount = 0; - - _DecodeRaw(Data, Decode); - - for (int i = 0; i < 3; i++) { - pBuffer[i] = Data.nData[i]; - if (pBuffer[i] != 255) nCount++; - } - - return nCount; -} - - -void Base64Coder::_EncodeToBuffer(const TempBucket& Decode, PBYTE pBuffer) { - TempBucket Data; - _EncodeRaw(Data, Decode); - - for (int i = 0; i < 4; i++) { - pBuffer[i] = Base64Digits[Data.nData[i]]; - } - - switch(Decode.nSize) { - case 1: - pBuffer[2] = '='; - case 2: - pBuffer[3] = '='; - } -} - -void Base64Coder::_DecodeRaw(TempBucket& Data, const TempBucket& Decode) { - BYTE nTemp; - - Data.nData[0] = Decode.nData[0]; - Data.nData[0] <<= 2; - - nTemp = Decode.nData[1]; - nTemp >>= 4; - nTemp &= 0x03; - Data.nData[0] |= nTemp; - - Data.nData[1] = Decode.nData[1]; - Data.nData[1] <<= 4; - - nTemp = Decode.nData[2]; - nTemp >>= 2; - nTemp &= 0x0F; - Data.nData[1] |= nTemp; - - Data.nData[2] = Decode.nData[2]; - Data.nData[2] <<= 6; - nTemp = Decode.nData[3]; - nTemp &= 0x3F; - Data.nData[2] |= nTemp; -} - -void Base64Coder::_EncodeRaw(TempBucket& Data, const TempBucket& Decode) { - BYTE nTemp; - - Data.nData[0] = Decode.nData[0]; - Data.nData[0] >>= 2; - - Data.nData[1] = Decode.nData[0]; - Data.nData[1] <<= 4; - nTemp = Decode.nData[1]; - nTemp >>= 4; - Data.nData[1] |= nTemp; - Data.nData[1] &= 0x3F; - - Data.nData[2] = Decode.nData[1]; - Data.nData[2] <<= 2; - - nTemp = Decode.nData[2]; - nTemp >>= 6; - - Data.nData[2] |= nTemp; - Data.nData[2] &= 0x3F; - - Data.nData[3] = Decode.nData[2]; - Data.nData[3] &= 0x3F; -} - -BOOL Base64Coder::_IsBadMimeChar(BYTE nData) { - switch (nData) { - case '\r': case '\n': case '\t': case ' ': - case '\b': case '\a': case '\f': case '\v': - return TRUE; - default: - return FALSE; - } -} - -void Base64Coder::_Init() { - for (int i = 0; i < 256; i++) { - Base64Coder::m_DecodeTable[i] = -2; - } - - for (int i = 0; i < 64; i++) { - Base64Coder::m_DecodeTable[Base64Digits[i]] = i; - Base64Coder::m_DecodeTable[Base64Digits[i]|0x80] = i; - } - - Base64Coder::m_DecodeTable['='] = -1; - Base64Coder::m_DecodeTable['='|0x80] = -1; - Base64Coder::m_Init = TRUE; +/* +** base64.cpp: implementation of the Base64Coder class. +** http://support.microsoft.com/kb/191239 +*/ + +#include "base64.h" + +static char Base64Digits[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +BOOL Base64Coder::m_Init = FALSE; +char Base64Coder::m_DecodeTable[256]; + +#ifndef PAGESIZE +#define PAGESIZE 4096 +#endif + +#ifndef ROUNDTOPAGE +#define ROUNDTOPAGE(a) (((a/4096)+1)*4096) +#endif + +// ============================================================================= + +Base64Coder::Base64Coder() : + m_pDBuffer(NULL), m_pEBuffer(NULL), + m_nDBufLen(0), m_nEBufLen(0) +{ +} + +Base64Coder::~Base64Coder() { + if (m_pDBuffer != NULL) delete [] m_pDBuffer; + if (m_pEBuffer != NULL) delete [] m_pEBuffer; +} + +// ============================================================================= + +LPCSTR Base64Coder::DecodedMessage() const { + return (LPCSTR)m_pDBuffer; +} + +LPCSTR Base64Coder::EncodedMessage() const { + return (LPCSTR)m_pEBuffer; +} + +void Base64Coder::AllocEncode(DWORD nSize) { + if (m_nEBufLen < nSize) { + if (m_pEBuffer != NULL) delete [] m_pEBuffer; + m_nEBufLen = ROUNDTOPAGE(nSize); + m_pEBuffer = new BYTE[m_nEBufLen]; + } + ::ZeroMemory(m_pEBuffer, m_nEBufLen); + m_nEDataLen = 0; +} + +void Base64Coder::AllocDecode(DWORD nSize) { + if (m_nDBufLen < nSize) { + if (m_pDBuffer != NULL) delete [] m_pDBuffer; + m_nDBufLen = ROUNDTOPAGE(nSize); + m_pDBuffer = new BYTE[m_nDBufLen]; + } + ::ZeroMemory(m_pDBuffer, m_nDBufLen); + m_nDDataLen = 0; +} + +void Base64Coder::SetEncodeBuffer(const PBYTE pBuffer, DWORD nBufLen) { + DWORD i = 0; + AllocEncode(nBufLen); + while (i < nBufLen) { + if (!_IsBadMimeChar(pBuffer[i])) { + m_pEBuffer[m_nEDataLen] = pBuffer[i]; + m_nEDataLen++; + } + i++; + } +} + +void Base64Coder::SetDecodeBuffer(const PBYTE pBuffer, DWORD nBufLen) { + AllocDecode(nBufLen); + ::CopyMemory(m_pDBuffer, pBuffer, nBufLen); + m_nDDataLen = nBufLen; +} + +void Base64Coder::Encode(const PBYTE pBuffer, DWORD nBufLen) { + SetDecodeBuffer(pBuffer, nBufLen); + AllocEncode(nBufLen * 2); + + TempBucket Raw; + DWORD nIndex = 0; + + while ((nIndex + 3) <= nBufLen) { + Raw.Clear(); + ::CopyMemory(&Raw, m_pDBuffer + nIndex, 3); + Raw.nSize = 3; + _EncodeToBuffer(Raw, m_pEBuffer + m_nEDataLen); + nIndex += 3; + m_nEDataLen += 4; + } + + if (nBufLen > nIndex) { + Raw.Clear(); + Raw.nSize = (BYTE)(nBufLen - nIndex); + ::CopyMemory(&Raw, m_pDBuffer + nIndex, nBufLen - nIndex); + _EncodeToBuffer(Raw, m_pEBuffer + m_nEDataLen); + m_nEDataLen += 4; + } +} + +void Base64Coder::Encode(LPCSTR szMessage) { + if (szMessage != NULL) { + Base64Coder::Encode((const PBYTE)szMessage, strlen(szMessage)); + } +} + +void Base64Coder::Decode(const PBYTE pBuffer, DWORD dwBufLen) { + if (!Base64Coder::m_Init) { + _Init(); + } + + SetEncodeBuffer(pBuffer, dwBufLen); + AllocDecode(dwBufLen); + + TempBucket Raw; + DWORD nIndex = 0; + + while ((nIndex + 4) <= m_nEDataLen) { + Raw.Clear(); + Raw.nData[0] = Base64Coder::m_DecodeTable[m_pEBuffer[nIndex]]; + Raw.nData[1] = Base64Coder::m_DecodeTable[m_pEBuffer[nIndex + 1]]; + Raw.nData[2] = Base64Coder::m_DecodeTable[m_pEBuffer[nIndex + 2]]; + Raw.nData[3] = Base64Coder::m_DecodeTable[m_pEBuffer[nIndex + 3]]; + + if (Raw.nData[2] == 255) Raw.nData[2] = 0; + if (Raw.nData[3] == 255) Raw.nData[3] = 0; + + Raw.nSize = 4; + _DecodeToBuffer(Raw, m_pDBuffer + m_nDDataLen); + nIndex += 4; + m_nDDataLen += 3; + } + + if (nIndex < m_nEDataLen) { + Raw.Clear(); + for (DWORD i = nIndex; i < m_nEDataLen; i++) { + Raw.nData[i - nIndex] = Base64Coder::m_DecodeTable[m_pEBuffer[i]]; + Raw.nSize++; + if (Raw.nData[i - nIndex] == 255) Raw.nData[i - nIndex] = 0; + } + _DecodeToBuffer(Raw, m_pDBuffer + m_nDDataLen); + m_nDDataLen += (m_nEDataLen - nIndex); + } +} + +void Base64Coder::Decode(LPCSTR szMessage) { + if (szMessage != NULL) { + Base64Coder::Decode((const PBYTE)szMessage, strlen(szMessage)); + } +} + +// ============================================================================= + +DWORD Base64Coder::_DecodeToBuffer(const TempBucket& Decode, PBYTE pBuffer) { + TempBucket Data; + DWORD nCount = 0; + + _DecodeRaw(Data, Decode); + + for (int i = 0; i < 3; i++) { + pBuffer[i] = Data.nData[i]; + if (pBuffer[i] != 255) nCount++; + } + + return nCount; +} + + +void Base64Coder::_EncodeToBuffer(const TempBucket& Decode, PBYTE pBuffer) { + TempBucket Data; + _EncodeRaw(Data, Decode); + + for (int i = 0; i < 4; i++) { + pBuffer[i] = Base64Digits[Data.nData[i]]; + } + + switch(Decode.nSize) { + case 1: + pBuffer[2] = '='; + case 2: + pBuffer[3] = '='; + } +} + +void Base64Coder::_DecodeRaw(TempBucket& Data, const TempBucket& Decode) { + BYTE nTemp; + + Data.nData[0] = Decode.nData[0]; + Data.nData[0] <<= 2; + + nTemp = Decode.nData[1]; + nTemp >>= 4; + nTemp &= 0x03; + Data.nData[0] |= nTemp; + + Data.nData[1] = Decode.nData[1]; + Data.nData[1] <<= 4; + + nTemp = Decode.nData[2]; + nTemp >>= 2; + nTemp &= 0x0F; + Data.nData[1] |= nTemp; + + Data.nData[2] = Decode.nData[2]; + Data.nData[2] <<= 6; + nTemp = Decode.nData[3]; + nTemp &= 0x3F; + Data.nData[2] |= nTemp; +} + +void Base64Coder::_EncodeRaw(TempBucket& Data, const TempBucket& Decode) { + BYTE nTemp; + + Data.nData[0] = Decode.nData[0]; + Data.nData[0] >>= 2; + + Data.nData[1] = Decode.nData[0]; + Data.nData[1] <<= 4; + nTemp = Decode.nData[1]; + nTemp >>= 4; + Data.nData[1] |= nTemp; + Data.nData[1] &= 0x3F; + + Data.nData[2] = Decode.nData[1]; + Data.nData[2] <<= 2; + + nTemp = Decode.nData[2]; + nTemp >>= 6; + + Data.nData[2] |= nTemp; + Data.nData[2] &= 0x3F; + + Data.nData[3] = Decode.nData[2]; + Data.nData[3] &= 0x3F; +} + +BOOL Base64Coder::_IsBadMimeChar(BYTE nData) { + switch (nData) { + case '\r': case '\n': case '\t': case ' ': + case '\b': case '\a': case '\f': case '\v': + return TRUE; + default: + return FALSE; + } +} + +void Base64Coder::_Init() { + for (int i = 0; i < 256; i++) { + Base64Coder::m_DecodeTable[i] = -2; + } + + for (int i = 0; i < 64; i++) { + Base64Coder::m_DecodeTable[Base64Digits[i]] = i; + Base64Coder::m_DecodeTable[Base64Digits[i]|0x80] = i; + } + + Base64Coder::m_DecodeTable['='] = -1; + Base64Coder::m_DecodeTable['='|0x80] = -1; + Base64Coder::m_Init = TRUE; } \ No newline at end of file diff --git a/third_party/base64/base64.h b/deps/src/base64/base64.h similarity index 96% rename from third_party/base64/base64.h rename to deps/src/base64/base64.h index efd451de7..1f896d9fe 100644 --- a/third_party/base64/base64.h +++ b/deps/src/base64/base64.h @@ -1,57 +1,57 @@ -/* -** base64.h: interface for the Base64Coder class. -** http://support.microsoft.com/kb/191239 -*/ - -#ifndef BASE64_H -#define BASE64_H - -#include - -// ============================================================================= - -class Base64Coder { - class TempBucket { - public: - BYTE nData[4]; - BYTE nSize; - void Clear() { ::ZeroMemory(nData, 4); nSize = 0; }; - }; - - PBYTE m_pDBuffer; - PBYTE m_pEBuffer; - DWORD m_nDBufLen; - DWORD m_nEBufLen; - DWORD m_nDDataLen; - DWORD m_nEDataLen; - -public: - Base64Coder(); - virtual ~Base64Coder(); - - virtual void Encode(const PBYTE, DWORD); - virtual void Decode(const PBYTE, DWORD); - virtual void Encode(LPCSTR sMessage); - virtual void Decode(LPCSTR sMessage); - - virtual LPCSTR DecodedMessage() const; - virtual LPCSTR EncodedMessage() const; - - virtual void AllocEncode(DWORD); - virtual void AllocDecode(DWORD); - virtual void SetEncodeBuffer(const PBYTE pBuffer, DWORD nBufLen); - virtual void SetDecodeBuffer(const PBYTE pBuffer, DWORD nBufLen); - -protected: - virtual void _EncodeToBuffer(const TempBucket& Decode, PBYTE pBuffer); - virtual ULONG _DecodeToBuffer(const TempBucket& Decode, PBYTE pBuffer); - virtual void _EncodeRaw(TempBucket&, const TempBucket&); - virtual void _DecodeRaw(TempBucket&, const TempBucket&); - virtual BOOL _IsBadMimeChar(BYTE); - - static char m_DecodeTable[256]; - static BOOL m_Init; - void _Init(); -}; - +/* +** base64.h: interface for the Base64Coder class. +** http://support.microsoft.com/kb/191239 +*/ + +#ifndef BASE64_H +#define BASE64_H + +#include + +// ============================================================================= + +class Base64Coder { + class TempBucket { + public: + BYTE nData[4]; + BYTE nSize; + void Clear() { ::ZeroMemory(nData, 4); nSize = 0; }; + }; + + PBYTE m_pDBuffer; + PBYTE m_pEBuffer; + DWORD m_nDBufLen; + DWORD m_nEBufLen; + DWORD m_nDDataLen; + DWORD m_nEDataLen; + +public: + Base64Coder(); + virtual ~Base64Coder(); + + virtual void Encode(const PBYTE, DWORD); + virtual void Decode(const PBYTE, DWORD); + virtual void Encode(LPCSTR sMessage); + virtual void Decode(LPCSTR sMessage); + + virtual LPCSTR DecodedMessage() const; + virtual LPCSTR EncodedMessage() const; + + virtual void AllocEncode(DWORD); + virtual void AllocDecode(DWORD); + virtual void SetEncodeBuffer(const PBYTE pBuffer, DWORD nBufLen); + virtual void SetDecodeBuffer(const PBYTE pBuffer, DWORD nBufLen); + +protected: + virtual void _EncodeToBuffer(const TempBucket& Decode, PBYTE pBuffer); + virtual ULONG _DecodeToBuffer(const TempBucket& Decode, PBYTE pBuffer); + virtual void _EncodeRaw(TempBucket&, const TempBucket&); + virtual void _DecodeRaw(TempBucket&, const TempBucket&); + virtual BOOL _IsBadMimeChar(BYTE); + + static char m_DecodeTable[256]; + static BOOL m_Init; + void _Init(); +}; + #endif // BASE64_H \ No newline at end of file diff --git a/deps/src/curl/curl.h b/deps/src/curl/curl.h new file mode 100644 index 000000000..8384191f1 --- /dev/null +++ b/deps/src/curl/curl.h @@ -0,0 +1,2318 @@ +#ifndef __CURL_CURL_H +#define __CURL_CURL_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/* + * If you have libcurl problems, all docs and details are found here: + * http://curl.haxx.se/libcurl/ + * + * curl-library mailing list subscription and unsubscription web interface: + * http://cool.haxx.se/mailman/listinfo/curl-library/ + */ + +#include "curlver.h" /* libcurl version defines */ +#include "curlbuild.h" /* libcurl build definitions */ +#include "curlrules.h" /* libcurl rules enforcement */ + +/* + * Define WIN32 when build target is Win32 API + */ + +#if (defined(_WIN32) || defined(__WIN32__)) && \ + !defined(WIN32) && !defined(__SYMBIAN32__) +#define WIN32 +#endif + +#include +#include + +#if defined(__FreeBSD__) && (__FreeBSD__ >= 2) +/* Needed for __FreeBSD_version symbol definition */ +#include +#endif + +/* The include stuff here below is mainly for time_t! */ +#include +#include + +#if defined(WIN32) && !defined(_WIN32_WCE) && !defined(__CYGWIN__) +#if !(defined(_WINSOCKAPI_) || defined(_WINSOCK_H) || defined(__LWIP_OPT_H__)) +/* The check above prevents the winsock2 inclusion if winsock.h already was + included, since they can't co-exist without problems */ +#include +#include +#endif +#endif + +/* HP-UX systems version 9, 10 and 11 lack sys/select.h and so does oldish + libc5-based Linux systems. Only include it on systems that are known to + require it! */ +#if defined(_AIX) || defined(__NOVELL_LIBC__) || defined(__NetBSD__) || \ + defined(__minix) || defined(__SYMBIAN32__) || defined(__INTEGRITY) || \ + defined(ANDROID) || defined(__ANDROID__) || defined(__OpenBSD__) || \ + (defined(__FreeBSD_version) && (__FreeBSD_version < 800000)) +#include +#endif + +#if !defined(WIN32) && !defined(_WIN32_WCE) +#include +#endif + +#if !defined(WIN32) && !defined(__WATCOMC__) && !defined(__VXWORKS__) +#include +#endif + +#ifdef __BEOS__ +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void CURL; + +/* + * libcurl external API function linkage decorations. + */ + +#ifdef CURL_STATICLIB +# define CURL_EXTERN +#elif defined(WIN32) || defined(_WIN32) || defined(__SYMBIAN32__) +# if defined(BUILDING_LIBCURL) +# define CURL_EXTERN __declspec(dllexport) +# else +# define CURL_EXTERN __declspec(dllimport) +# endif +#elif defined(BUILDING_LIBCURL) && defined(CURL_HIDDEN_SYMBOLS) +# define CURL_EXTERN CURL_EXTERN_SYMBOL +#else +# define CURL_EXTERN +#endif + +#ifndef curl_socket_typedef +/* socket typedef */ +#if defined(WIN32) && !defined(__LWIP_OPT_H__) +typedef SOCKET curl_socket_t; +#define CURL_SOCKET_BAD INVALID_SOCKET +#else +typedef int curl_socket_t; +#define CURL_SOCKET_BAD -1 +#endif +#define curl_socket_typedef +#endif /* curl_socket_typedef */ + +struct curl_httppost { + struct curl_httppost *next; /* next entry in the list */ + char *name; /* pointer to allocated name */ + long namelength; /* length of name length */ + char *contents; /* pointer to allocated data contents */ + long contentslength; /* length of contents field */ + char *buffer; /* pointer to allocated buffer contents */ + long bufferlength; /* length of buffer field */ + char *contenttype; /* Content-Type */ + struct curl_slist* contentheader; /* list of extra headers for this form */ + struct curl_httppost *more; /* if one field name has more than one + file, this link should link to following + files */ + long flags; /* as defined below */ +#define HTTPPOST_FILENAME (1<<0) /* specified content is a file name */ +#define HTTPPOST_READFILE (1<<1) /* specified content is a file name */ +#define HTTPPOST_PTRNAME (1<<2) /* name is only stored pointer + do not free in formfree */ +#define HTTPPOST_PTRCONTENTS (1<<3) /* contents is only stored pointer + do not free in formfree */ +#define HTTPPOST_BUFFER (1<<4) /* upload file from buffer */ +#define HTTPPOST_PTRBUFFER (1<<5) /* upload file from pointer contents */ +#define HTTPPOST_CALLBACK (1<<6) /* upload file contents by using the + regular read callback to get the data + and pass the given pointer as custom + pointer */ + + char *showfilename; /* The file name to show. If not set, the + actual file name will be used (if this + is a file part) */ + void *userp; /* custom pointer used for + HTTPPOST_CALLBACK posts */ +}; + +/* This is the CURLOPT_PROGRESSFUNCTION callback proto. It is now considered + deprecated but was the only choice up until 7.31.0 */ +typedef int (*curl_progress_callback)(void *clientp, + double dltotal, + double dlnow, + double ultotal, + double ulnow); + +/* This is the CURLOPT_XFERINFOFUNCTION callback proto. It was introduced in + 7.32.0, it avoids floating point and provides more detailed information. */ +typedef int (*curl_xferinfo_callback)(void *clientp, + curl_off_t dltotal, + curl_off_t dlnow, + curl_off_t ultotal, + curl_off_t ulnow); + +#ifndef CURL_MAX_WRITE_SIZE + /* Tests have proven that 20K is a very bad buffer size for uploads on + Windows, while 16K for some odd reason performed a lot better. + We do the ifndef check to allow this value to easier be changed at build + time for those who feel adventurous. The practical minimum is about + 400 bytes since libcurl uses a buffer of this size as a scratch area + (unrelated to network send operations). */ +#define CURL_MAX_WRITE_SIZE 16384 +#endif + +#ifndef CURL_MAX_HTTP_HEADER +/* The only reason to have a max limit for this is to avoid the risk of a bad + server feeding libcurl with a never-ending header that will cause reallocs + infinitely */ +#define CURL_MAX_HTTP_HEADER (100*1024) +#endif + +/* This is a magic return code for the write callback that, when returned, + will signal libcurl to pause receiving on the current transfer. */ +#define CURL_WRITEFUNC_PAUSE 0x10000001 + +typedef size_t (*curl_write_callback)(char *buffer, + size_t size, + size_t nitems, + void *outstream); + + + +/* enumeration of file types */ +typedef enum { + CURLFILETYPE_FILE = 0, + CURLFILETYPE_DIRECTORY, + CURLFILETYPE_SYMLINK, + CURLFILETYPE_DEVICE_BLOCK, + CURLFILETYPE_DEVICE_CHAR, + CURLFILETYPE_NAMEDPIPE, + CURLFILETYPE_SOCKET, + CURLFILETYPE_DOOR, /* is possible only on Sun Solaris now */ + + CURLFILETYPE_UNKNOWN /* should never occur */ +} curlfiletype; + +#define CURLFINFOFLAG_KNOWN_FILENAME (1<<0) +#define CURLFINFOFLAG_KNOWN_FILETYPE (1<<1) +#define CURLFINFOFLAG_KNOWN_TIME (1<<2) +#define CURLFINFOFLAG_KNOWN_PERM (1<<3) +#define CURLFINFOFLAG_KNOWN_UID (1<<4) +#define CURLFINFOFLAG_KNOWN_GID (1<<5) +#define CURLFINFOFLAG_KNOWN_SIZE (1<<6) +#define CURLFINFOFLAG_KNOWN_HLINKCOUNT (1<<7) + +/* Content of this structure depends on information which is known and is + achievable (e.g. by FTP LIST parsing). Please see the url_easy_setopt(3) man + page for callbacks returning this structure -- some fields are mandatory, + some others are optional. The FLAG field has special meaning. */ +struct curl_fileinfo { + char *filename; + curlfiletype filetype; + time_t time; + unsigned int perm; + int uid; + int gid; + curl_off_t size; + long int hardlinks; + + struct { + /* If some of these fields is not NULL, it is a pointer to b_data. */ + char *time; + char *perm; + char *user; + char *group; + char *target; /* pointer to the target filename of a symlink */ + } strings; + + unsigned int flags; + + /* used internally */ + char * b_data; + size_t b_size; + size_t b_used; +}; + +/* return codes for CURLOPT_CHUNK_BGN_FUNCTION */ +#define CURL_CHUNK_BGN_FUNC_OK 0 +#define CURL_CHUNK_BGN_FUNC_FAIL 1 /* tell the lib to end the task */ +#define CURL_CHUNK_BGN_FUNC_SKIP 2 /* skip this chunk over */ + +/* if splitting of data transfer is enabled, this callback is called before + download of an individual chunk started. Note that parameter "remains" works + only for FTP wildcard downloading (for now), otherwise is not used */ +typedef long (*curl_chunk_bgn_callback)(const void *transfer_info, + void *ptr, + int remains); + +/* return codes for CURLOPT_CHUNK_END_FUNCTION */ +#define CURL_CHUNK_END_FUNC_OK 0 +#define CURL_CHUNK_END_FUNC_FAIL 1 /* tell the lib to end the task */ + +/* If splitting of data transfer is enabled this callback is called after + download of an individual chunk finished. + Note! After this callback was set then it have to be called FOR ALL chunks. + Even if downloading of this chunk was skipped in CHUNK_BGN_FUNC. + This is the reason why we don't need "transfer_info" parameter in this + callback and we are not interested in "remains" parameter too. */ +typedef long (*curl_chunk_end_callback)(void *ptr); + +/* return codes for FNMATCHFUNCTION */ +#define CURL_FNMATCHFUNC_MATCH 0 /* string corresponds to the pattern */ +#define CURL_FNMATCHFUNC_NOMATCH 1 /* pattern doesn't match the string */ +#define CURL_FNMATCHFUNC_FAIL 2 /* an error occurred */ + +/* callback type for wildcard downloading pattern matching. If the + string matches the pattern, return CURL_FNMATCHFUNC_MATCH value, etc. */ +typedef int (*curl_fnmatch_callback)(void *ptr, + const char *pattern, + const char *string); + +/* These are the return codes for the seek callbacks */ +#define CURL_SEEKFUNC_OK 0 +#define CURL_SEEKFUNC_FAIL 1 /* fail the entire transfer */ +#define CURL_SEEKFUNC_CANTSEEK 2 /* tell libcurl seeking can't be done, so + libcurl might try other means instead */ +typedef int (*curl_seek_callback)(void *instream, + curl_off_t offset, + int origin); /* 'whence' */ + +/* This is a return code for the read callback that, when returned, will + signal libcurl to immediately abort the current transfer. */ +#define CURL_READFUNC_ABORT 0x10000000 +/* This is a return code for the read callback that, when returned, will + signal libcurl to pause sending data on the current transfer. */ +#define CURL_READFUNC_PAUSE 0x10000001 + +typedef size_t (*curl_read_callback)(char *buffer, + size_t size, + size_t nitems, + void *instream); + +typedef enum { + CURLSOCKTYPE_IPCXN, /* socket created for a specific IP connection */ + CURLSOCKTYPE_ACCEPT, /* socket created by accept() call */ + CURLSOCKTYPE_LAST /* never use */ +} curlsocktype; + +/* The return code from the sockopt_callback can signal information back + to libcurl: */ +#define CURL_SOCKOPT_OK 0 +#define CURL_SOCKOPT_ERROR 1 /* causes libcurl to abort and return + CURLE_ABORTED_BY_CALLBACK */ +#define CURL_SOCKOPT_ALREADY_CONNECTED 2 + +typedef int (*curl_sockopt_callback)(void *clientp, + curl_socket_t curlfd, + curlsocktype purpose); + +struct curl_sockaddr { + int family; + int socktype; + int protocol; + unsigned int addrlen; /* addrlen was a socklen_t type before 7.18.0 but it + turned really ugly and painful on the systems that + lack this type */ + struct sockaddr addr; +}; + +typedef curl_socket_t +(*curl_opensocket_callback)(void *clientp, + curlsocktype purpose, + struct curl_sockaddr *address); + +typedef int +(*curl_closesocket_callback)(void *clientp, curl_socket_t item); + +typedef enum { + CURLIOE_OK, /* I/O operation successful */ + CURLIOE_UNKNOWNCMD, /* command was unknown to callback */ + CURLIOE_FAILRESTART, /* failed to restart the read */ + CURLIOE_LAST /* never use */ +} curlioerr; + +typedef enum { + CURLIOCMD_NOP, /* no operation */ + CURLIOCMD_RESTARTREAD, /* restart the read stream from start */ + CURLIOCMD_LAST /* never use */ +} curliocmd; + +typedef curlioerr (*curl_ioctl_callback)(CURL *handle, + int cmd, + void *clientp); + +/* + * The following typedef's are signatures of malloc, free, realloc, strdup and + * calloc respectively. Function pointers of these types can be passed to the + * curl_global_init_mem() function to set user defined memory management + * callback routines. + */ +typedef void *(*curl_malloc_callback)(size_t size); +typedef void (*curl_free_callback)(void *ptr); +typedef void *(*curl_realloc_callback)(void *ptr, size_t size); +typedef char *(*curl_strdup_callback)(const char *str); +typedef void *(*curl_calloc_callback)(size_t nmemb, size_t size); + +/* the kind of data that is passed to information_callback*/ +typedef enum { + CURLINFO_TEXT = 0, + CURLINFO_HEADER_IN, /* 1 */ + CURLINFO_HEADER_OUT, /* 2 */ + CURLINFO_DATA_IN, /* 3 */ + CURLINFO_DATA_OUT, /* 4 */ + CURLINFO_SSL_DATA_IN, /* 5 */ + CURLINFO_SSL_DATA_OUT, /* 6 */ + CURLINFO_END +} curl_infotype; + +typedef int (*curl_debug_callback) + (CURL *handle, /* the handle/transfer this concerns */ + curl_infotype type, /* what kind of data */ + char *data, /* points to the data */ + size_t size, /* size of the data pointed to */ + void *userptr); /* whatever the user please */ + +/* All possible error codes from all sorts of curl functions. Future versions + may return other values, stay prepared. + + Always add new return codes last. Never *EVER* remove any. The return + codes must remain the same! + */ + +typedef enum { + CURLE_OK = 0, + CURLE_UNSUPPORTED_PROTOCOL, /* 1 */ + CURLE_FAILED_INIT, /* 2 */ + CURLE_URL_MALFORMAT, /* 3 */ + CURLE_NOT_BUILT_IN, /* 4 - [was obsoleted in August 2007 for + 7.17.0, reused in April 2011 for 7.21.5] */ + CURLE_COULDNT_RESOLVE_PROXY, /* 5 */ + CURLE_COULDNT_RESOLVE_HOST, /* 6 */ + CURLE_COULDNT_CONNECT, /* 7 */ + CURLE_FTP_WEIRD_SERVER_REPLY, /* 8 */ + CURLE_REMOTE_ACCESS_DENIED, /* 9 a service was denied by the server + due to lack of access - when login fails + this is not returned. */ + CURLE_FTP_ACCEPT_FAILED, /* 10 - [was obsoleted in April 2006 for + 7.15.4, reused in Dec 2011 for 7.24.0]*/ + CURLE_FTP_WEIRD_PASS_REPLY, /* 11 */ + CURLE_FTP_ACCEPT_TIMEOUT, /* 12 - timeout occurred accepting server + [was obsoleted in August 2007 for 7.17.0, + reused in Dec 2011 for 7.24.0]*/ + CURLE_FTP_WEIRD_PASV_REPLY, /* 13 */ + CURLE_FTP_WEIRD_227_FORMAT, /* 14 */ + CURLE_FTP_CANT_GET_HOST, /* 15 */ + CURLE_OBSOLETE16, /* 16 - NOT USED */ + CURLE_FTP_COULDNT_SET_TYPE, /* 17 */ + CURLE_PARTIAL_FILE, /* 18 */ + CURLE_FTP_COULDNT_RETR_FILE, /* 19 */ + CURLE_OBSOLETE20, /* 20 - NOT USED */ + CURLE_QUOTE_ERROR, /* 21 - quote command failure */ + CURLE_HTTP_RETURNED_ERROR, /* 22 */ + CURLE_WRITE_ERROR, /* 23 */ + CURLE_OBSOLETE24, /* 24 - NOT USED */ + CURLE_UPLOAD_FAILED, /* 25 - failed upload "command" */ + CURLE_READ_ERROR, /* 26 - couldn't open/read from file */ + CURLE_OUT_OF_MEMORY, /* 27 */ + /* Note: CURLE_OUT_OF_MEMORY may sometimes indicate a conversion error + instead of a memory allocation error if CURL_DOES_CONVERSIONS + is defined + */ + CURLE_OPERATION_TIMEDOUT, /* 28 - the timeout time was reached */ + CURLE_OBSOLETE29, /* 29 - NOT USED */ + CURLE_FTP_PORT_FAILED, /* 30 - FTP PORT operation failed */ + CURLE_FTP_COULDNT_USE_REST, /* 31 - the REST command failed */ + CURLE_OBSOLETE32, /* 32 - NOT USED */ + CURLE_RANGE_ERROR, /* 33 - RANGE "command" didn't work */ + CURLE_HTTP_POST_ERROR, /* 34 */ + CURLE_SSL_CONNECT_ERROR, /* 35 - wrong when connecting with SSL */ + CURLE_BAD_DOWNLOAD_RESUME, /* 36 - couldn't resume download */ + CURLE_FILE_COULDNT_READ_FILE, /* 37 */ + CURLE_LDAP_CANNOT_BIND, /* 38 */ + CURLE_LDAP_SEARCH_FAILED, /* 39 */ + CURLE_OBSOLETE40, /* 40 - NOT USED */ + CURLE_FUNCTION_NOT_FOUND, /* 41 */ + CURLE_ABORTED_BY_CALLBACK, /* 42 */ + CURLE_BAD_FUNCTION_ARGUMENT, /* 43 */ + CURLE_OBSOLETE44, /* 44 - NOT USED */ + CURLE_INTERFACE_FAILED, /* 45 - CURLOPT_INTERFACE failed */ + CURLE_OBSOLETE46, /* 46 - NOT USED */ + CURLE_TOO_MANY_REDIRECTS , /* 47 - catch endless re-direct loops */ + CURLE_UNKNOWN_OPTION, /* 48 - User specified an unknown option */ + CURLE_TELNET_OPTION_SYNTAX , /* 49 - Malformed telnet option */ + CURLE_OBSOLETE50, /* 50 - NOT USED */ + CURLE_PEER_FAILED_VERIFICATION, /* 51 - peer's certificate or fingerprint + wasn't verified fine */ + CURLE_GOT_NOTHING, /* 52 - when this is a specific error */ + CURLE_SSL_ENGINE_NOTFOUND, /* 53 - SSL crypto engine not found */ + CURLE_SSL_ENGINE_SETFAILED, /* 54 - can not set SSL crypto engine as + default */ + CURLE_SEND_ERROR, /* 55 - failed sending network data */ + CURLE_RECV_ERROR, /* 56 - failure in receiving network data */ + CURLE_OBSOLETE57, /* 57 - NOT IN USE */ + CURLE_SSL_CERTPROBLEM, /* 58 - problem with the local certificate */ + CURLE_SSL_CIPHER, /* 59 - couldn't use specified cipher */ + CURLE_SSL_CACERT, /* 60 - problem with the CA cert (path?) */ + CURLE_BAD_CONTENT_ENCODING, /* 61 - Unrecognized/bad encoding */ + CURLE_LDAP_INVALID_URL, /* 62 - Invalid LDAP URL */ + CURLE_FILESIZE_EXCEEDED, /* 63 - Maximum file size exceeded */ + CURLE_USE_SSL_FAILED, /* 64 - Requested FTP SSL level failed */ + CURLE_SEND_FAIL_REWIND, /* 65 - Sending the data requires a rewind + that failed */ + CURLE_SSL_ENGINE_INITFAILED, /* 66 - failed to initialise ENGINE */ + CURLE_LOGIN_DENIED, /* 67 - user, password or similar was not + accepted and we failed to login */ + CURLE_TFTP_NOTFOUND, /* 68 - file not found on server */ + CURLE_TFTP_PERM, /* 69 - permission problem on server */ + CURLE_REMOTE_DISK_FULL, /* 70 - out of disk space on server */ + CURLE_TFTP_ILLEGAL, /* 71 - Illegal TFTP operation */ + CURLE_TFTP_UNKNOWNID, /* 72 - Unknown transfer ID */ + CURLE_REMOTE_FILE_EXISTS, /* 73 - File already exists */ + CURLE_TFTP_NOSUCHUSER, /* 74 - No such user */ + CURLE_CONV_FAILED, /* 75 - conversion failed */ + CURLE_CONV_REQD, /* 76 - caller must register conversion + callbacks using curl_easy_setopt options + CURLOPT_CONV_FROM_NETWORK_FUNCTION, + CURLOPT_CONV_TO_NETWORK_FUNCTION, and + CURLOPT_CONV_FROM_UTF8_FUNCTION */ + CURLE_SSL_CACERT_BADFILE, /* 77 - could not load CACERT file, missing + or wrong format */ + CURLE_REMOTE_FILE_NOT_FOUND, /* 78 - remote file not found */ + CURLE_SSH, /* 79 - error from the SSH layer, somewhat + generic so the error message will be of + interest when this has happened */ + + CURLE_SSL_SHUTDOWN_FAILED, /* 80 - Failed to shut down the SSL + connection */ + CURLE_AGAIN, /* 81 - socket is not ready for send/recv, + wait till it's ready and try again (Added + in 7.18.2) */ + CURLE_SSL_CRL_BADFILE, /* 82 - could not load CRL file, missing or + wrong format (Added in 7.19.0) */ + CURLE_SSL_ISSUER_ERROR, /* 83 - Issuer check failed. (Added in + 7.19.0) */ + CURLE_FTP_PRET_FAILED, /* 84 - a PRET command failed */ + CURLE_RTSP_CSEQ_ERROR, /* 85 - mismatch of RTSP CSeq numbers */ + CURLE_RTSP_SESSION_ERROR, /* 86 - mismatch of RTSP Session Ids */ + CURLE_FTP_BAD_FILE_LIST, /* 87 - unable to parse FTP file list */ + CURLE_CHUNK_FAILED, /* 88 - chunk callback reported error */ + CURLE_NO_CONNECTION_AVAILABLE, /* 89 - No connection available, the + session will be queued */ + CURL_LAST /* never use! */ +} CURLcode; + +#ifndef CURL_NO_OLDIES /* define this to test if your app builds with all + the obsolete stuff removed! */ + +/* Previously obsoletes error codes re-used in 7.24.0 */ +#define CURLE_OBSOLETE10 CURLE_FTP_ACCEPT_FAILED +#define CURLE_OBSOLETE12 CURLE_FTP_ACCEPT_TIMEOUT + +/* compatibility with older names */ +#define CURLOPT_ENCODING CURLOPT_ACCEPT_ENCODING + +/* The following were added in 7.21.5, April 2011 */ +#define CURLE_UNKNOWN_TELNET_OPTION CURLE_UNKNOWN_OPTION + +/* The following were added in 7.17.1 */ +/* These are scheduled to disappear by 2009 */ +#define CURLE_SSL_PEER_CERTIFICATE CURLE_PEER_FAILED_VERIFICATION + +/* The following were added in 7.17.0 */ +/* These are scheduled to disappear by 2009 */ +#define CURLE_OBSOLETE CURLE_OBSOLETE50 /* no one should be using this! */ +#define CURLE_BAD_PASSWORD_ENTERED CURLE_OBSOLETE46 +#define CURLE_BAD_CALLING_ORDER CURLE_OBSOLETE44 +#define CURLE_FTP_USER_PASSWORD_INCORRECT CURLE_OBSOLETE10 +#define CURLE_FTP_CANT_RECONNECT CURLE_OBSOLETE16 +#define CURLE_FTP_COULDNT_GET_SIZE CURLE_OBSOLETE32 +#define CURLE_FTP_COULDNT_SET_ASCII CURLE_OBSOLETE29 +#define CURLE_FTP_WEIRD_USER_REPLY CURLE_OBSOLETE12 +#define CURLE_FTP_WRITE_ERROR CURLE_OBSOLETE20 +#define CURLE_LIBRARY_NOT_FOUND CURLE_OBSOLETE40 +#define CURLE_MALFORMAT_USER CURLE_OBSOLETE24 +#define CURLE_SHARE_IN_USE CURLE_OBSOLETE57 +#define CURLE_URL_MALFORMAT_USER CURLE_NOT_BUILT_IN + +#define CURLE_FTP_ACCESS_DENIED CURLE_REMOTE_ACCESS_DENIED +#define CURLE_FTP_COULDNT_SET_BINARY CURLE_FTP_COULDNT_SET_TYPE +#define CURLE_FTP_QUOTE_ERROR CURLE_QUOTE_ERROR +#define CURLE_TFTP_DISKFULL CURLE_REMOTE_DISK_FULL +#define CURLE_TFTP_EXISTS CURLE_REMOTE_FILE_EXISTS +#define CURLE_HTTP_RANGE_ERROR CURLE_RANGE_ERROR +#define CURLE_FTP_SSL_FAILED CURLE_USE_SSL_FAILED + +/* The following were added earlier */ + +#define CURLE_OPERATION_TIMEOUTED CURLE_OPERATION_TIMEDOUT + +#define CURLE_HTTP_NOT_FOUND CURLE_HTTP_RETURNED_ERROR +#define CURLE_HTTP_PORT_FAILED CURLE_INTERFACE_FAILED +#define CURLE_FTP_COULDNT_STOR_FILE CURLE_UPLOAD_FAILED + +#define CURLE_FTP_PARTIAL_FILE CURLE_PARTIAL_FILE +#define CURLE_FTP_BAD_DOWNLOAD_RESUME CURLE_BAD_DOWNLOAD_RESUME + +/* This was the error code 50 in 7.7.3 and a few earlier versions, this + is no longer used by libcurl but is instead #defined here only to not + make programs break */ +#define CURLE_ALREADY_COMPLETE 99999 + +#endif /*!CURL_NO_OLDIES*/ + +/* This prototype applies to all conversion callbacks */ +typedef CURLcode (*curl_conv_callback)(char *buffer, size_t length); + +typedef CURLcode (*curl_ssl_ctx_callback)(CURL *curl, /* easy handle */ + void *ssl_ctx, /* actually an + OpenSSL SSL_CTX */ + void *userptr); + +typedef enum { + CURLPROXY_HTTP = 0, /* added in 7.10, new in 7.19.4 default is to use + CONNECT HTTP/1.1 */ + CURLPROXY_HTTP_1_0 = 1, /* added in 7.19.4, force to use CONNECT + HTTP/1.0 */ + CURLPROXY_SOCKS4 = 4, /* support added in 7.15.2, enum existed already + in 7.10 */ + CURLPROXY_SOCKS5 = 5, /* added in 7.10 */ + CURLPROXY_SOCKS4A = 6, /* added in 7.18.0 */ + CURLPROXY_SOCKS5_HOSTNAME = 7 /* Use the SOCKS5 protocol but pass along the + host name rather than the IP address. added + in 7.18.0 */ +} curl_proxytype; /* this enum was added in 7.10 */ + +/* + * Bitmasks for CURLOPT_HTTPAUTH and CURLOPT_PROXYAUTH options: + * + * CURLAUTH_NONE - No HTTP authentication + * CURLAUTH_BASIC - HTTP Basic authentication (default) + * CURLAUTH_DIGEST - HTTP Digest authentication + * CURLAUTH_GSSNEGOTIATE - HTTP GSS-Negotiate authentication + * CURLAUTH_NTLM - HTTP NTLM authentication + * CURLAUTH_DIGEST_IE - HTTP Digest authentication with IE flavour + * CURLAUTH_NTLM_WB - HTTP NTLM authentication delegated to winbind helper + * CURLAUTH_ONLY - Use together with a single other type to force no + * authentication or just that single type + * CURLAUTH_ANY - All fine types set + * CURLAUTH_ANYSAFE - All fine types except Basic + */ + +#define CURLAUTH_NONE ((unsigned long)0) +#define CURLAUTH_BASIC (((unsigned long)1)<<0) +#define CURLAUTH_DIGEST (((unsigned long)1)<<1) +#define CURLAUTH_GSSNEGOTIATE (((unsigned long)1)<<2) +#define CURLAUTH_NTLM (((unsigned long)1)<<3) +#define CURLAUTH_DIGEST_IE (((unsigned long)1)<<4) +#define CURLAUTH_NTLM_WB (((unsigned long)1)<<5) +#define CURLAUTH_ONLY (((unsigned long)1)<<31) +#define CURLAUTH_ANY (~CURLAUTH_DIGEST_IE) +#define CURLAUTH_ANYSAFE (~(CURLAUTH_BASIC|CURLAUTH_DIGEST_IE)) + +#define CURLSSH_AUTH_ANY ~0 /* all types supported by the server */ +#define CURLSSH_AUTH_NONE 0 /* none allowed, silly but complete */ +#define CURLSSH_AUTH_PUBLICKEY (1<<0) /* public/private key files */ +#define CURLSSH_AUTH_PASSWORD (1<<1) /* password */ +#define CURLSSH_AUTH_HOST (1<<2) /* host key files */ +#define CURLSSH_AUTH_KEYBOARD (1<<3) /* keyboard interactive */ +#define CURLSSH_AUTH_AGENT (1<<4) /* agent (ssh-agent, pageant...) */ +#define CURLSSH_AUTH_DEFAULT CURLSSH_AUTH_ANY + +#define CURLGSSAPI_DELEGATION_NONE 0 /* no delegation (default) */ +#define CURLGSSAPI_DELEGATION_POLICY_FLAG (1<<0) /* if permitted by policy */ +#define CURLGSSAPI_DELEGATION_FLAG (1<<1) /* delegate always */ + +#define CURL_ERROR_SIZE 256 + +enum curl_khtype { + CURLKHTYPE_UNKNOWN, + CURLKHTYPE_RSA1, + CURLKHTYPE_RSA, + CURLKHTYPE_DSS +}; + +struct curl_khkey { + const char *key; /* points to a zero-terminated string encoded with base64 + if len is zero, otherwise to the "raw" data */ + size_t len; + enum curl_khtype keytype; +}; + +/* this is the set of return values expected from the curl_sshkeycallback + callback */ +enum curl_khstat { + CURLKHSTAT_FINE_ADD_TO_FILE, + CURLKHSTAT_FINE, + CURLKHSTAT_REJECT, /* reject the connection, return an error */ + CURLKHSTAT_DEFER, /* do not accept it, but we can't answer right now so + this causes a CURLE_DEFER error but otherwise the + connection will be left intact etc */ + CURLKHSTAT_LAST /* not for use, only a marker for last-in-list */ +}; + +/* this is the set of status codes pass in to the callback */ +enum curl_khmatch { + CURLKHMATCH_OK, /* match */ + CURLKHMATCH_MISMATCH, /* host found, key mismatch! */ + CURLKHMATCH_MISSING, /* no matching host/key found */ + CURLKHMATCH_LAST /* not for use, only a marker for last-in-list */ +}; + +typedef int + (*curl_sshkeycallback) (CURL *easy, /* easy handle */ + const struct curl_khkey *knownkey, /* known */ + const struct curl_khkey *foundkey, /* found */ + enum curl_khmatch, /* libcurl's view on the keys */ + void *clientp); /* custom pointer passed from app */ + +/* parameter for the CURLOPT_USE_SSL option */ +typedef enum { + CURLUSESSL_NONE, /* do not attempt to use SSL */ + CURLUSESSL_TRY, /* try using SSL, proceed anyway otherwise */ + CURLUSESSL_CONTROL, /* SSL for the control connection or fail */ + CURLUSESSL_ALL, /* SSL for all communication or fail */ + CURLUSESSL_LAST /* not an option, never use */ +} curl_usessl; + +/* Definition of bits for the CURLOPT_SSL_OPTIONS argument: */ + +/* - ALLOW_BEAST tells libcurl to allow the BEAST SSL vulnerability in the + name of improving interoperability with older servers. Some SSL libraries + have introduced work-arounds for this flaw but those work-arounds sometimes + make the SSL communication fail. To regain functionality with those broken + servers, a user can this way allow the vulnerability back. */ +#define CURLSSLOPT_ALLOW_BEAST (1<<0) + +#ifndef CURL_NO_OLDIES /* define this to test if your app builds with all + the obsolete stuff removed! */ + +/* Backwards compatibility with older names */ +/* These are scheduled to disappear by 2009 */ + +#define CURLFTPSSL_NONE CURLUSESSL_NONE +#define CURLFTPSSL_TRY CURLUSESSL_TRY +#define CURLFTPSSL_CONTROL CURLUSESSL_CONTROL +#define CURLFTPSSL_ALL CURLUSESSL_ALL +#define CURLFTPSSL_LAST CURLUSESSL_LAST +#define curl_ftpssl curl_usessl +#endif /*!CURL_NO_OLDIES*/ + +/* parameter for the CURLOPT_FTP_SSL_CCC option */ +typedef enum { + CURLFTPSSL_CCC_NONE, /* do not send CCC */ + CURLFTPSSL_CCC_PASSIVE, /* Let the server initiate the shutdown */ + CURLFTPSSL_CCC_ACTIVE, /* Initiate the shutdown */ + CURLFTPSSL_CCC_LAST /* not an option, never use */ +} curl_ftpccc; + +/* parameter for the CURLOPT_FTPSSLAUTH option */ +typedef enum { + CURLFTPAUTH_DEFAULT, /* let libcurl decide */ + CURLFTPAUTH_SSL, /* use "AUTH SSL" */ + CURLFTPAUTH_TLS, /* use "AUTH TLS" */ + CURLFTPAUTH_LAST /* not an option, never use */ +} curl_ftpauth; + +/* parameter for the CURLOPT_FTP_CREATE_MISSING_DIRS option */ +typedef enum { + CURLFTP_CREATE_DIR_NONE, /* do NOT create missing dirs! */ + CURLFTP_CREATE_DIR, /* (FTP/SFTP) if CWD fails, try MKD and then CWD + again if MKD succeeded, for SFTP this does + similar magic */ + CURLFTP_CREATE_DIR_RETRY, /* (FTP only) if CWD fails, try MKD and then CWD + again even if MKD failed! */ + CURLFTP_CREATE_DIR_LAST /* not an option, never use */ +} curl_ftpcreatedir; + +/* parameter for the CURLOPT_FTP_FILEMETHOD option */ +typedef enum { + CURLFTPMETHOD_DEFAULT, /* let libcurl pick */ + CURLFTPMETHOD_MULTICWD, /* single CWD operation for each path part */ + CURLFTPMETHOD_NOCWD, /* no CWD at all */ + CURLFTPMETHOD_SINGLECWD, /* one CWD to full dir, then work on file */ + CURLFTPMETHOD_LAST /* not an option, never use */ +} curl_ftpmethod; + +/* bitmask defines for CURLOPT_HEADEROPT */ +#define CURLHEADER_UNIFIED 0 +#define CURLHEADER_SEPARATE (1<<0) + +/* CURLPROTO_ defines are for the CURLOPT_*PROTOCOLS options */ +#define CURLPROTO_HTTP (1<<0) +#define CURLPROTO_HTTPS (1<<1) +#define CURLPROTO_FTP (1<<2) +#define CURLPROTO_FTPS (1<<3) +#define CURLPROTO_SCP (1<<4) +#define CURLPROTO_SFTP (1<<5) +#define CURLPROTO_TELNET (1<<6) +#define CURLPROTO_LDAP (1<<7) +#define CURLPROTO_LDAPS (1<<8) +#define CURLPROTO_DICT (1<<9) +#define CURLPROTO_FILE (1<<10) +#define CURLPROTO_TFTP (1<<11) +#define CURLPROTO_IMAP (1<<12) +#define CURLPROTO_IMAPS (1<<13) +#define CURLPROTO_POP3 (1<<14) +#define CURLPROTO_POP3S (1<<15) +#define CURLPROTO_SMTP (1<<16) +#define CURLPROTO_SMTPS (1<<17) +#define CURLPROTO_RTSP (1<<18) +#define CURLPROTO_RTMP (1<<19) +#define CURLPROTO_RTMPT (1<<20) +#define CURLPROTO_RTMPE (1<<21) +#define CURLPROTO_RTMPTE (1<<22) +#define CURLPROTO_RTMPS (1<<23) +#define CURLPROTO_RTMPTS (1<<24) +#define CURLPROTO_GOPHER (1<<25) +#define CURLPROTO_ALL (~0) /* enable everything */ + +/* long may be 32 or 64 bits, but we should never depend on anything else + but 32 */ +#define CURLOPTTYPE_LONG 0 +#define CURLOPTTYPE_OBJECTPOINT 10000 +#define CURLOPTTYPE_FUNCTIONPOINT 20000 +#define CURLOPTTYPE_OFF_T 30000 + +/* name is uppercase CURLOPT_, + type is one of the defined CURLOPTTYPE_ + number is unique identifier */ +#ifdef CINIT +#undef CINIT +#endif + +#ifdef CURL_ISOCPP +#define CINIT(na,t,nu) CURLOPT_ ## na = CURLOPTTYPE_ ## t + nu +#else +/* The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */ +#define LONG CURLOPTTYPE_LONG +#define OBJECTPOINT CURLOPTTYPE_OBJECTPOINT +#define FUNCTIONPOINT CURLOPTTYPE_FUNCTIONPOINT +#define OFF_T CURLOPTTYPE_OFF_T +#define CINIT(name,type,number) CURLOPT_/**/name = type + number +#endif + +/* + * This macro-mania below setups the CURLOPT_[what] enum, to be used with + * curl_easy_setopt(). The first argument in the CINIT() macro is the [what] + * word. + */ + +typedef enum { + /* This is the FILE * or void * the regular output should be written to. */ + CINIT(FILE, OBJECTPOINT, 1), + + /* The full URL to get/put */ + CINIT(URL, OBJECTPOINT, 2), + + /* Port number to connect to, if other than default. */ + CINIT(PORT, LONG, 3), + + /* Name of proxy to use. */ + CINIT(PROXY, OBJECTPOINT, 4), + + /* "user:password;options" to use when fetching. */ + CINIT(USERPWD, OBJECTPOINT, 5), + + /* "user:password" to use with proxy. */ + CINIT(PROXYUSERPWD, OBJECTPOINT, 6), + + /* Range to get, specified as an ASCII string. */ + CINIT(RANGE, OBJECTPOINT, 7), + + /* not used */ + + /* Specified file stream to upload from (use as input): */ + CINIT(INFILE, OBJECTPOINT, 9), + + /* Buffer to receive error messages in, must be at least CURL_ERROR_SIZE + * bytes big. If this is not used, error messages go to stderr instead: */ + CINIT(ERRORBUFFER, OBJECTPOINT, 10), + + /* Function that will be called to store the output (instead of fwrite). The + * parameters will use fwrite() syntax, make sure to follow them. */ + CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11), + + /* Function that will be called to read the input (instead of fread). The + * parameters will use fread() syntax, make sure to follow them. */ + CINIT(READFUNCTION, FUNCTIONPOINT, 12), + + /* Time-out the read operation after this amount of seconds */ + CINIT(TIMEOUT, LONG, 13), + + /* If the CURLOPT_INFILE is used, this can be used to inform libcurl about + * how large the file being sent really is. That allows better error + * checking and better verifies that the upload was successful. -1 means + * unknown size. + * + * For large file support, there is also a _LARGE version of the key + * which takes an off_t type, allowing platforms with larger off_t + * sizes to handle larger files. See below for INFILESIZE_LARGE. + */ + CINIT(INFILESIZE, LONG, 14), + + /* POST static input fields. */ + CINIT(POSTFIELDS, OBJECTPOINT, 15), + + /* Set the referrer page (needed by some CGIs) */ + CINIT(REFERER, OBJECTPOINT, 16), + + /* Set the FTP PORT string (interface name, named or numerical IP address) + Use i.e '-' to use default address. */ + CINIT(FTPPORT, OBJECTPOINT, 17), + + /* Set the User-Agent string (examined by some CGIs) */ + CINIT(USERAGENT, OBJECTPOINT, 18), + + /* If the download receives less than "low speed limit" bytes/second + * during "low speed time" seconds, the operations is aborted. + * You could i.e if you have a pretty high speed connection, abort if + * it is less than 2000 bytes/sec during 20 seconds. + */ + + /* Set the "low speed limit" */ + CINIT(LOW_SPEED_LIMIT, LONG, 19), + + /* Set the "low speed time" */ + CINIT(LOW_SPEED_TIME, LONG, 20), + + /* Set the continuation offset. + * + * Note there is also a _LARGE version of this key which uses + * off_t types, allowing for large file offsets on platforms which + * use larger-than-32-bit off_t's. Look below for RESUME_FROM_LARGE. + */ + CINIT(RESUME_FROM, LONG, 21), + + /* Set cookie in request: */ + CINIT(COOKIE, OBJECTPOINT, 22), + + /* This points to a linked list of headers, struct curl_slist kind. This + list is also used for RTSP (in spite of its name) */ + CINIT(HTTPHEADER, OBJECTPOINT, 23), + + /* This points to a linked list of post entries, struct curl_httppost */ + CINIT(HTTPPOST, OBJECTPOINT, 24), + + /* name of the file keeping your private SSL-certificate */ + CINIT(SSLCERT, OBJECTPOINT, 25), + + /* password for the SSL or SSH private key */ + CINIT(KEYPASSWD, OBJECTPOINT, 26), + + /* send TYPE parameter? */ + CINIT(CRLF, LONG, 27), + + /* send linked-list of QUOTE commands */ + CINIT(QUOTE, OBJECTPOINT, 28), + + /* send FILE * or void * to store headers to, if you use a callback it + is simply passed to the callback unmodified */ + CINIT(WRITEHEADER, OBJECTPOINT, 29), + + /* point to a file to read the initial cookies from, also enables + "cookie awareness" */ + CINIT(COOKIEFILE, OBJECTPOINT, 31), + + /* What version to specifically try to use. + See CURL_SSLVERSION defines below. */ + CINIT(SSLVERSION, LONG, 32), + + /* What kind of HTTP time condition to use, see defines */ + CINIT(TIMECONDITION, LONG, 33), + + /* Time to use with the above condition. Specified in number of seconds + since 1 Jan 1970 */ + CINIT(TIMEVALUE, LONG, 34), + + /* 35 = OBSOLETE */ + + /* Custom request, for customizing the get command like + HTTP: DELETE, TRACE and others + FTP: to use a different list command + */ + CINIT(CUSTOMREQUEST, OBJECTPOINT, 36), + + /* HTTP request, for odd commands like DELETE, TRACE and others */ + CINIT(STDERR, OBJECTPOINT, 37), + + /* 38 is not used */ + + /* send linked-list of post-transfer QUOTE commands */ + CINIT(POSTQUOTE, OBJECTPOINT, 39), + + CINIT(WRITEINFO, OBJECTPOINT, 40), /* DEPRECATED, do not use! */ + + CINIT(VERBOSE, LONG, 41), /* talk a lot */ + CINIT(HEADER, LONG, 42), /* throw the header out too */ + CINIT(NOPROGRESS, LONG, 43), /* shut off the progress meter */ + CINIT(NOBODY, LONG, 44), /* use HEAD to get http document */ + CINIT(FAILONERROR, LONG, 45), /* no output on http error codes >= 300 */ + CINIT(UPLOAD, LONG, 46), /* this is an upload */ + CINIT(POST, LONG, 47), /* HTTP POST method */ + CINIT(DIRLISTONLY, LONG, 48), /* bare names when listing directories */ + + CINIT(APPEND, LONG, 50), /* Append instead of overwrite on upload! */ + + /* Specify whether to read the user+password from the .netrc or the URL. + * This must be one of the CURL_NETRC_* enums below. */ + CINIT(NETRC, LONG, 51), + + CINIT(FOLLOWLOCATION, LONG, 52), /* use Location: Luke! */ + + CINIT(TRANSFERTEXT, LONG, 53), /* transfer data in text/ASCII format */ + CINIT(PUT, LONG, 54), /* HTTP PUT */ + + /* 55 = OBSOLETE */ + + /* DEPRECATED + * Function that will be called instead of the internal progress display + * function. This function should be defined as the curl_progress_callback + * prototype defines. */ + CINIT(PROGRESSFUNCTION, FUNCTIONPOINT, 56), + + /* Data passed to the CURLOPT_PROGRESSFUNCTION and CURLOPT_XFERINFOFUNCTION + callbacks */ + CINIT(PROGRESSDATA, OBJECTPOINT, 57), +#define CURLOPT_XFERINFODATA CURLOPT_PROGRESSDATA + + /* We want the referrer field set automatically when following locations */ + CINIT(AUTOREFERER, LONG, 58), + + /* Port of the proxy, can be set in the proxy string as well with: + "[host]:[port]" */ + CINIT(PROXYPORT, LONG, 59), + + /* size of the POST input data, if strlen() is not good to use */ + CINIT(POSTFIELDSIZE, LONG, 60), + + /* tunnel non-http operations through a HTTP proxy */ + CINIT(HTTPPROXYTUNNEL, LONG, 61), + + /* Set the interface string to use as outgoing network interface */ + CINIT(INTERFACE, OBJECTPOINT, 62), + + /* Set the krb4/5 security level, this also enables krb4/5 awareness. This + * is a string, 'clear', 'safe', 'confidential' or 'private'. If the string + * is set but doesn't match one of these, 'private' will be used. */ + CINIT(KRBLEVEL, OBJECTPOINT, 63), + + /* Set if we should verify the peer in ssl handshake, set 1 to verify. */ + CINIT(SSL_VERIFYPEER, LONG, 64), + + /* The CApath or CAfile used to validate the peer certificate + this option is used only if SSL_VERIFYPEER is true */ + CINIT(CAINFO, OBJECTPOINT, 65), + + /* 66 = OBSOLETE */ + /* 67 = OBSOLETE */ + + /* Maximum number of http redirects to follow */ + CINIT(MAXREDIRS, LONG, 68), + + /* Pass a long set to 1 to get the date of the requested document (if + possible)! Pass a zero to shut it off. */ + CINIT(FILETIME, LONG, 69), + + /* This points to a linked list of telnet options */ + CINIT(TELNETOPTIONS, OBJECTPOINT, 70), + + /* Max amount of cached alive connections */ + CINIT(MAXCONNECTS, LONG, 71), + + CINIT(CLOSEPOLICY, LONG, 72), /* DEPRECATED, do not use! */ + + /* 73 = OBSOLETE */ + + /* Set to explicitly use a new connection for the upcoming transfer. + Do not use this unless you're absolutely sure of this, as it makes the + operation slower and is less friendly for the network. */ + CINIT(FRESH_CONNECT, LONG, 74), + + /* Set to explicitly forbid the upcoming transfer's connection to be re-used + when done. Do not use this unless you're absolutely sure of this, as it + makes the operation slower and is less friendly for the network. */ + CINIT(FORBID_REUSE, LONG, 75), + + /* Set to a file name that contains random data for libcurl to use to + seed the random engine when doing SSL connects. */ + CINIT(RANDOM_FILE, OBJECTPOINT, 76), + + /* Set to the Entropy Gathering Daemon socket pathname */ + CINIT(EGDSOCKET, OBJECTPOINT, 77), + + /* Time-out connect operations after this amount of seconds, if connects are + OK within this time, then fine... This only aborts the connect phase. */ + CINIT(CONNECTTIMEOUT, LONG, 78), + + /* Function that will be called to store headers (instead of fwrite). The + * parameters will use fwrite() syntax, make sure to follow them. */ + CINIT(HEADERFUNCTION, FUNCTIONPOINT, 79), + + /* Set this to force the HTTP request to get back to GET. Only really usable + if POST, PUT or a custom request have been used first. + */ + CINIT(HTTPGET, LONG, 80), + + /* Set if we should verify the Common name from the peer certificate in ssl + * handshake, set 1 to check existence, 2 to ensure that it matches the + * provided hostname. */ + CINIT(SSL_VERIFYHOST, LONG, 81), + + /* Specify which file name to write all known cookies in after completed + operation. Set file name to "-" (dash) to make it go to stdout. */ + CINIT(COOKIEJAR, OBJECTPOINT, 82), + + /* Specify which SSL ciphers to use */ + CINIT(SSL_CIPHER_LIST, OBJECTPOINT, 83), + + /* Specify which HTTP version to use! This must be set to one of the + CURL_HTTP_VERSION* enums set below. */ + CINIT(HTTP_VERSION, LONG, 84), + + /* Specifically switch on or off the FTP engine's use of the EPSV command. By + default, that one will always be attempted before the more traditional + PASV command. */ + CINIT(FTP_USE_EPSV, LONG, 85), + + /* type of the file keeping your SSL-certificate ("DER", "PEM", "ENG") */ + CINIT(SSLCERTTYPE, OBJECTPOINT, 86), + + /* name of the file keeping your private SSL-key */ + CINIT(SSLKEY, OBJECTPOINT, 87), + + /* type of the file keeping your private SSL-key ("DER", "PEM", "ENG") */ + CINIT(SSLKEYTYPE, OBJECTPOINT, 88), + + /* crypto engine for the SSL-sub system */ + CINIT(SSLENGINE, OBJECTPOINT, 89), + + /* set the crypto engine for the SSL-sub system as default + the param has no meaning... + */ + CINIT(SSLENGINE_DEFAULT, LONG, 90), + + /* Non-zero value means to use the global dns cache */ + CINIT(DNS_USE_GLOBAL_CACHE, LONG, 91), /* DEPRECATED, do not use! */ + + /* DNS cache timeout */ + CINIT(DNS_CACHE_TIMEOUT, LONG, 92), + + /* send linked-list of pre-transfer QUOTE commands */ + CINIT(PREQUOTE, OBJECTPOINT, 93), + + /* set the debug function */ + CINIT(DEBUGFUNCTION, FUNCTIONPOINT, 94), + + /* set the data for the debug function */ + CINIT(DEBUGDATA, OBJECTPOINT, 95), + + /* mark this as start of a cookie session */ + CINIT(COOKIESESSION, LONG, 96), + + /* The CApath directory used to validate the peer certificate + this option is used only if SSL_VERIFYPEER is true */ + CINIT(CAPATH, OBJECTPOINT, 97), + + /* Instruct libcurl to use a smaller receive buffer */ + CINIT(BUFFERSIZE, LONG, 98), + + /* Instruct libcurl to not use any signal/alarm handlers, even when using + timeouts. This option is useful for multi-threaded applications. + See libcurl-the-guide for more background information. */ + CINIT(NOSIGNAL, LONG, 99), + + /* Provide a CURLShare for mutexing non-ts data */ + CINIT(SHARE, OBJECTPOINT, 100), + + /* indicates type of proxy. accepted values are CURLPROXY_HTTP (default), + CURLPROXY_SOCKS4, CURLPROXY_SOCKS4A and CURLPROXY_SOCKS5. */ + CINIT(PROXYTYPE, LONG, 101), + + /* Set the Accept-Encoding string. Use this to tell a server you would like + the response to be compressed. Before 7.21.6, this was known as + CURLOPT_ENCODING */ + CINIT(ACCEPT_ENCODING, OBJECTPOINT, 102), + + /* Set pointer to private data */ + CINIT(PRIVATE, OBJECTPOINT, 103), + + /* Set aliases for HTTP 200 in the HTTP Response header */ + CINIT(HTTP200ALIASES, OBJECTPOINT, 104), + + /* Continue to send authentication (user+password) when following locations, + even when hostname changed. This can potentially send off the name + and password to whatever host the server decides. */ + CINIT(UNRESTRICTED_AUTH, LONG, 105), + + /* Specifically switch on or off the FTP engine's use of the EPRT command ( + it also disables the LPRT attempt). By default, those ones will always be + attempted before the good old traditional PORT command. */ + CINIT(FTP_USE_EPRT, LONG, 106), + + /* Set this to a bitmask value to enable the particular authentications + methods you like. Use this in combination with CURLOPT_USERPWD. + Note that setting multiple bits may cause extra network round-trips. */ + CINIT(HTTPAUTH, LONG, 107), + + /* Set the ssl context callback function, currently only for OpenSSL ssl_ctx + in second argument. The function must be matching the + curl_ssl_ctx_callback proto. */ + CINIT(SSL_CTX_FUNCTION, FUNCTIONPOINT, 108), + + /* Set the userdata for the ssl context callback function's third + argument */ + CINIT(SSL_CTX_DATA, OBJECTPOINT, 109), + + /* FTP Option that causes missing dirs to be created on the remote server. + In 7.19.4 we introduced the convenience enums for this option using the + CURLFTP_CREATE_DIR prefix. + */ + CINIT(FTP_CREATE_MISSING_DIRS, LONG, 110), + + /* Set this to a bitmask value to enable the particular authentications + methods you like. Use this in combination with CURLOPT_PROXYUSERPWD. + Note that setting multiple bits may cause extra network round-trips. */ + CINIT(PROXYAUTH, LONG, 111), + + /* FTP option that changes the timeout, in seconds, associated with + getting a response. This is different from transfer timeout time and + essentially places a demand on the FTP server to acknowledge commands + in a timely manner. */ + CINIT(FTP_RESPONSE_TIMEOUT, LONG, 112), +#define CURLOPT_SERVER_RESPONSE_TIMEOUT CURLOPT_FTP_RESPONSE_TIMEOUT + + /* Set this option to one of the CURL_IPRESOLVE_* defines (see below) to + tell libcurl to resolve names to those IP versions only. This only has + affect on systems with support for more than one, i.e IPv4 _and_ IPv6. */ + CINIT(IPRESOLVE, LONG, 113), + + /* Set this option to limit the size of a file that will be downloaded from + an HTTP or FTP server. + + Note there is also _LARGE version which adds large file support for + platforms which have larger off_t sizes. See MAXFILESIZE_LARGE below. */ + CINIT(MAXFILESIZE, LONG, 114), + + /* See the comment for INFILESIZE above, but in short, specifies + * the size of the file being uploaded. -1 means unknown. + */ + CINIT(INFILESIZE_LARGE, OFF_T, 115), + + /* Sets the continuation offset. There is also a LONG version of this; + * look above for RESUME_FROM. + */ + CINIT(RESUME_FROM_LARGE, OFF_T, 116), + + /* Sets the maximum size of data that will be downloaded from + * an HTTP or FTP server. See MAXFILESIZE above for the LONG version. + */ + CINIT(MAXFILESIZE_LARGE, OFF_T, 117), + + /* Set this option to the file name of your .netrc file you want libcurl + to parse (using the CURLOPT_NETRC option). If not set, libcurl will do + a poor attempt to find the user's home directory and check for a .netrc + file in there. */ + CINIT(NETRC_FILE, OBJECTPOINT, 118), + + /* Enable SSL/TLS for FTP, pick one of: + CURLUSESSL_TRY - try using SSL, proceed anyway otherwise + CURLUSESSL_CONTROL - SSL for the control connection or fail + CURLUSESSL_ALL - SSL for all communication or fail + */ + CINIT(USE_SSL, LONG, 119), + + /* The _LARGE version of the standard POSTFIELDSIZE option */ + CINIT(POSTFIELDSIZE_LARGE, OFF_T, 120), + + /* Enable/disable the TCP Nagle algorithm */ + CINIT(TCP_NODELAY, LONG, 121), + + /* 122 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */ + /* 123 OBSOLETE. Gone in 7.16.0 */ + /* 124 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */ + /* 125 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */ + /* 126 OBSOLETE, used in 7.12.3. Gone in 7.13.0 */ + /* 127 OBSOLETE. Gone in 7.16.0 */ + /* 128 OBSOLETE. Gone in 7.16.0 */ + + /* When FTP over SSL/TLS is selected (with CURLOPT_USE_SSL), this option + can be used to change libcurl's default action which is to first try + "AUTH SSL" and then "AUTH TLS" in this order, and proceed when a OK + response has been received. + + Available parameters are: + CURLFTPAUTH_DEFAULT - let libcurl decide + CURLFTPAUTH_SSL - try "AUTH SSL" first, then TLS + CURLFTPAUTH_TLS - try "AUTH TLS" first, then SSL + */ + CINIT(FTPSSLAUTH, LONG, 129), + + CINIT(IOCTLFUNCTION, FUNCTIONPOINT, 130), + CINIT(IOCTLDATA, OBJECTPOINT, 131), + + /* 132 OBSOLETE. Gone in 7.16.0 */ + /* 133 OBSOLETE. Gone in 7.16.0 */ + + /* zero terminated string for pass on to the FTP server when asked for + "account" info */ + CINIT(FTP_ACCOUNT, OBJECTPOINT, 134), + + /* feed cookies into cookie engine */ + CINIT(COOKIELIST, OBJECTPOINT, 135), + + /* ignore Content-Length */ + CINIT(IGNORE_CONTENT_LENGTH, LONG, 136), + + /* Set to non-zero to skip the IP address received in a 227 PASV FTP server + response. Typically used for FTP-SSL purposes but is not restricted to + that. libcurl will then instead use the same IP address it used for the + control connection. */ + CINIT(FTP_SKIP_PASV_IP, LONG, 137), + + /* Select "file method" to use when doing FTP, see the curl_ftpmethod + above. */ + CINIT(FTP_FILEMETHOD, LONG, 138), + + /* Local port number to bind the socket to */ + CINIT(LOCALPORT, LONG, 139), + + /* Number of ports to try, including the first one set with LOCALPORT. + Thus, setting it to 1 will make no additional attempts but the first. + */ + CINIT(LOCALPORTRANGE, LONG, 140), + + /* no transfer, set up connection and let application use the socket by + extracting it with CURLINFO_LASTSOCKET */ + CINIT(CONNECT_ONLY, LONG, 141), + + /* Function that will be called to convert from the + network encoding (instead of using the iconv calls in libcurl) */ + CINIT(CONV_FROM_NETWORK_FUNCTION, FUNCTIONPOINT, 142), + + /* Function that will be called to convert to the + network encoding (instead of using the iconv calls in libcurl) */ + CINIT(CONV_TO_NETWORK_FUNCTION, FUNCTIONPOINT, 143), + + /* Function that will be called to convert from UTF8 + (instead of using the iconv calls in libcurl) + Note that this is used only for SSL certificate processing */ + CINIT(CONV_FROM_UTF8_FUNCTION, FUNCTIONPOINT, 144), + + /* if the connection proceeds too quickly then need to slow it down */ + /* limit-rate: maximum number of bytes per second to send or receive */ + CINIT(MAX_SEND_SPEED_LARGE, OFF_T, 145), + CINIT(MAX_RECV_SPEED_LARGE, OFF_T, 146), + + /* Pointer to command string to send if USER/PASS fails. */ + CINIT(FTP_ALTERNATIVE_TO_USER, OBJECTPOINT, 147), + + /* callback function for setting socket options */ + CINIT(SOCKOPTFUNCTION, FUNCTIONPOINT, 148), + CINIT(SOCKOPTDATA, OBJECTPOINT, 149), + + /* set to 0 to disable session ID re-use for this transfer, default is + enabled (== 1) */ + CINIT(SSL_SESSIONID_CACHE, LONG, 150), + + /* allowed SSH authentication methods */ + CINIT(SSH_AUTH_TYPES, LONG, 151), + + /* Used by scp/sftp to do public/private key authentication */ + CINIT(SSH_PUBLIC_KEYFILE, OBJECTPOINT, 152), + CINIT(SSH_PRIVATE_KEYFILE, OBJECTPOINT, 153), + + /* Send CCC (Clear Command Channel) after authentication */ + CINIT(FTP_SSL_CCC, LONG, 154), + + /* Same as TIMEOUT and CONNECTTIMEOUT, but with ms resolution */ + CINIT(TIMEOUT_MS, LONG, 155), + CINIT(CONNECTTIMEOUT_MS, LONG, 156), + + /* set to zero to disable the libcurl's decoding and thus pass the raw body + data to the application even when it is encoded/compressed */ + CINIT(HTTP_TRANSFER_DECODING, LONG, 157), + CINIT(HTTP_CONTENT_DECODING, LONG, 158), + + /* Permission used when creating new files and directories on the remote + server for protocols that support it, SFTP/SCP/FILE */ + CINIT(NEW_FILE_PERMS, LONG, 159), + CINIT(NEW_DIRECTORY_PERMS, LONG, 160), + + /* Set the behaviour of POST when redirecting. Values must be set to one + of CURL_REDIR* defines below. This used to be called CURLOPT_POST301 */ + CINIT(POSTREDIR, LONG, 161), + + /* used by scp/sftp to verify the host's public key */ + CINIT(SSH_HOST_PUBLIC_KEY_MD5, OBJECTPOINT, 162), + + /* Callback function for opening socket (instead of socket(2)). Optionally, + callback is able change the address or refuse to connect returning + CURL_SOCKET_BAD. The callback should have type + curl_opensocket_callback */ + CINIT(OPENSOCKETFUNCTION, FUNCTIONPOINT, 163), + CINIT(OPENSOCKETDATA, OBJECTPOINT, 164), + + /* POST volatile input fields. */ + CINIT(COPYPOSTFIELDS, OBJECTPOINT, 165), + + /* set transfer mode (;type=) when doing FTP via an HTTP proxy */ + CINIT(PROXY_TRANSFER_MODE, LONG, 166), + + /* Callback function for seeking in the input stream */ + CINIT(SEEKFUNCTION, FUNCTIONPOINT, 167), + CINIT(SEEKDATA, OBJECTPOINT, 168), + + /* CRL file */ + CINIT(CRLFILE, OBJECTPOINT, 169), + + /* Issuer certificate */ + CINIT(ISSUERCERT, OBJECTPOINT, 170), + + /* (IPv6) Address scope */ + CINIT(ADDRESS_SCOPE, LONG, 171), + + /* Collect certificate chain info and allow it to get retrievable with + CURLINFO_CERTINFO after the transfer is complete. */ + CINIT(CERTINFO, LONG, 172), + + /* "name" and "pwd" to use when fetching. */ + CINIT(USERNAME, OBJECTPOINT, 173), + CINIT(PASSWORD, OBJECTPOINT, 174), + + /* "name" and "pwd" to use with Proxy when fetching. */ + CINIT(PROXYUSERNAME, OBJECTPOINT, 175), + CINIT(PROXYPASSWORD, OBJECTPOINT, 176), + + /* Comma separated list of hostnames defining no-proxy zones. These should + match both hostnames directly, and hostnames within a domain. For + example, local.com will match local.com and www.local.com, but NOT + notlocal.com or www.notlocal.com. For compatibility with other + implementations of this, .local.com will be considered to be the same as + local.com. A single * is the only valid wildcard, and effectively + disables the use of proxy. */ + CINIT(NOPROXY, OBJECTPOINT, 177), + + /* block size for TFTP transfers */ + CINIT(TFTP_BLKSIZE, LONG, 178), + + /* Socks Service */ + CINIT(SOCKS5_GSSAPI_SERVICE, OBJECTPOINT, 179), + + /* Socks Service */ + CINIT(SOCKS5_GSSAPI_NEC, LONG, 180), + + /* set the bitmask for the protocols that are allowed to be used for the + transfer, which thus helps the app which takes URLs from users or other + external inputs and want to restrict what protocol(s) to deal + with. Defaults to CURLPROTO_ALL. */ + CINIT(PROTOCOLS, LONG, 181), + + /* set the bitmask for the protocols that libcurl is allowed to follow to, + as a subset of the CURLOPT_PROTOCOLS ones. That means the protocol needs + to be set in both bitmasks to be allowed to get redirected to. Defaults + to all protocols except FILE and SCP. */ + CINIT(REDIR_PROTOCOLS, LONG, 182), + + /* set the SSH knownhost file name to use */ + CINIT(SSH_KNOWNHOSTS, OBJECTPOINT, 183), + + /* set the SSH host key callback, must point to a curl_sshkeycallback + function */ + CINIT(SSH_KEYFUNCTION, FUNCTIONPOINT, 184), + + /* set the SSH host key callback custom pointer */ + CINIT(SSH_KEYDATA, OBJECTPOINT, 185), + + /* set the SMTP mail originator */ + CINIT(MAIL_FROM, OBJECTPOINT, 186), + + /* set the SMTP mail receiver(s) */ + CINIT(MAIL_RCPT, OBJECTPOINT, 187), + + /* FTP: send PRET before PASV */ + CINIT(FTP_USE_PRET, LONG, 188), + + /* RTSP request method (OPTIONS, SETUP, PLAY, etc...) */ + CINIT(RTSP_REQUEST, LONG, 189), + + /* The RTSP session identifier */ + CINIT(RTSP_SESSION_ID, OBJECTPOINT, 190), + + /* The RTSP stream URI */ + CINIT(RTSP_STREAM_URI, OBJECTPOINT, 191), + + /* The Transport: header to use in RTSP requests */ + CINIT(RTSP_TRANSPORT, OBJECTPOINT, 192), + + /* Manually initialize the client RTSP CSeq for this handle */ + CINIT(RTSP_CLIENT_CSEQ, LONG, 193), + + /* Manually initialize the server RTSP CSeq for this handle */ + CINIT(RTSP_SERVER_CSEQ, LONG, 194), + + /* The stream to pass to INTERLEAVEFUNCTION. */ + CINIT(INTERLEAVEDATA, OBJECTPOINT, 195), + + /* Let the application define a custom write method for RTP data */ + CINIT(INTERLEAVEFUNCTION, FUNCTIONPOINT, 196), + + /* Turn on wildcard matching */ + CINIT(WILDCARDMATCH, LONG, 197), + + /* Directory matching callback called before downloading of an + individual file (chunk) started */ + CINIT(CHUNK_BGN_FUNCTION, FUNCTIONPOINT, 198), + + /* Directory matching callback called after the file (chunk) + was downloaded, or skipped */ + CINIT(CHUNK_END_FUNCTION, FUNCTIONPOINT, 199), + + /* Change match (fnmatch-like) callback for wildcard matching */ + CINIT(FNMATCH_FUNCTION, FUNCTIONPOINT, 200), + + /* Let the application define custom chunk data pointer */ + CINIT(CHUNK_DATA, OBJECTPOINT, 201), + + /* FNMATCH_FUNCTION user pointer */ + CINIT(FNMATCH_DATA, OBJECTPOINT, 202), + + /* send linked-list of name:port:address sets */ + CINIT(RESOLVE, OBJECTPOINT, 203), + + /* Set a username for authenticated TLS */ + CINIT(TLSAUTH_USERNAME, OBJECTPOINT, 204), + + /* Set a password for authenticated TLS */ + CINIT(TLSAUTH_PASSWORD, OBJECTPOINT, 205), + + /* Set authentication type for authenticated TLS */ + CINIT(TLSAUTH_TYPE, OBJECTPOINT, 206), + + /* Set to 1 to enable the "TE:" header in HTTP requests to ask for + compressed transfer-encoded responses. Set to 0 to disable the use of TE: + in outgoing requests. The current default is 0, but it might change in a + future libcurl release. + + libcurl will ask for the compressed methods it knows of, and if that + isn't any, it will not ask for transfer-encoding at all even if this + option is set to 1. + + */ + CINIT(TRANSFER_ENCODING, LONG, 207), + + /* Callback function for closing socket (instead of close(2)). The callback + should have type curl_closesocket_callback */ + CINIT(CLOSESOCKETFUNCTION, FUNCTIONPOINT, 208), + CINIT(CLOSESOCKETDATA, OBJECTPOINT, 209), + + /* allow GSSAPI credential delegation */ + CINIT(GSSAPI_DELEGATION, LONG, 210), + + /* Set the name servers to use for DNS resolution */ + CINIT(DNS_SERVERS, OBJECTPOINT, 211), + + /* Time-out accept operations (currently for FTP only) after this amount + of miliseconds. */ + CINIT(ACCEPTTIMEOUT_MS, LONG, 212), + + /* Set TCP keepalive */ + CINIT(TCP_KEEPALIVE, LONG, 213), + + /* non-universal keepalive knobs (Linux, AIX, HP-UX, more) */ + CINIT(TCP_KEEPIDLE, LONG, 214), + CINIT(TCP_KEEPINTVL, LONG, 215), + + /* Enable/disable specific SSL features with a bitmask, see CURLSSLOPT_* */ + CINIT(SSL_OPTIONS, LONG, 216), + + /* Set the SMTP auth originator */ + CINIT(MAIL_AUTH, OBJECTPOINT, 217), + + /* Enable/disable SASL initial response */ + CINIT(SASL_IR, LONG, 218), + + /* Function that will be called instead of the internal progress display + * function. This function should be defined as the curl_xferinfo_callback + * prototype defines. (Deprecates CURLOPT_PROGRESSFUNCTION) */ + CINIT(XFERINFOFUNCTION, FUNCTIONPOINT, 219), + + /* The XOAUTH2 bearer token */ + CINIT(XOAUTH2_BEARER, OBJECTPOINT, 220), + + /* Set the interface string to use as outgoing network + * interface for DNS requests. + * Only supported by the c-ares DNS backend */ + CINIT(DNS_INTERFACE, OBJECTPOINT, 221), + + /* Set the local IPv4 address to use for outgoing DNS requests. + * Only supported by the c-ares DNS backend */ + CINIT(DNS_LOCAL_IP4, OBJECTPOINT, 222), + + /* Set the local IPv4 address to use for outgoing DNS requests. + * Only supported by the c-ares DNS backend */ + CINIT(DNS_LOCAL_IP6, OBJECTPOINT, 223), + + /* Set authentication options directly */ + CINIT(LOGIN_OPTIONS, OBJECTPOINT, 224), + + /* Enable/disable TLS NPN extension (http2 over ssl might fail without) */ + CINIT(SSL_ENABLE_NPN, LONG, 225), + + /* Enable/disable TLS ALPN extension (http2 over ssl might fail without) */ + CINIT(SSL_ENABLE_ALPN, LONG, 226), + + /* Time to wait for a response to a HTTP request containing an + * Expect: 100-continue header before sending the data anyway. */ + CINIT(EXPECT_100_TIMEOUT_MS, LONG, 227), + + /* This points to a linked list of headers used for proxy requests only, + struct curl_slist kind */ + CINIT(PROXYHEADER, OBJECTPOINT, 228), + + /* Pass in a bitmask of "header options" */ + CINIT(HEADEROPT, LONG, 229), + + CURLOPT_LASTENTRY /* the last unused */ +} CURLoption; + +#ifndef CURL_NO_OLDIES /* define this to test if your app builds with all + the obsolete stuff removed! */ + +/* Backwards compatibility with older names */ +/* These are scheduled to disappear by 2011 */ + +/* This was added in version 7.19.1 */ +#define CURLOPT_POST301 CURLOPT_POSTREDIR + +/* These are scheduled to disappear by 2009 */ + +/* The following were added in 7.17.0 */ +#define CURLOPT_SSLKEYPASSWD CURLOPT_KEYPASSWD +#define CURLOPT_FTPAPPEND CURLOPT_APPEND +#define CURLOPT_FTPLISTONLY CURLOPT_DIRLISTONLY +#define CURLOPT_FTP_SSL CURLOPT_USE_SSL + +/* The following were added earlier */ + +#define CURLOPT_SSLCERTPASSWD CURLOPT_KEYPASSWD +#define CURLOPT_KRB4LEVEL CURLOPT_KRBLEVEL + +#else +/* This is set if CURL_NO_OLDIES is defined at compile-time */ +#undef CURLOPT_DNS_USE_GLOBAL_CACHE /* soon obsolete */ +#endif + + + /* Below here follows defines for the CURLOPT_IPRESOLVE option. If a host + name resolves addresses using more than one IP protocol version, this + option might be handy to force libcurl to use a specific IP version. */ +#define CURL_IPRESOLVE_WHATEVER 0 /* default, resolves addresses to all IP + versions that your system allows */ +#define CURL_IPRESOLVE_V4 1 /* resolve to ipv4 addresses */ +#define CURL_IPRESOLVE_V6 2 /* resolve to ipv6 addresses */ + + /* three convenient "aliases" that follow the name scheme better */ +#define CURLOPT_WRITEDATA CURLOPT_FILE +#define CURLOPT_READDATA CURLOPT_INFILE +#define CURLOPT_HEADERDATA CURLOPT_WRITEHEADER +#define CURLOPT_RTSPHEADER CURLOPT_HTTPHEADER + + /* These enums are for use with the CURLOPT_HTTP_VERSION option. */ +enum { + CURL_HTTP_VERSION_NONE, /* setting this means we don't care, and that we'd + like the library to choose the best possible + for us! */ + CURL_HTTP_VERSION_1_0, /* please use HTTP 1.0 in the request */ + CURL_HTTP_VERSION_1_1, /* please use HTTP 1.1 in the request */ + CURL_HTTP_VERSION_2_0, /* please use HTTP 2.0 in the request */ + + CURL_HTTP_VERSION_LAST /* *ILLEGAL* http version */ +}; + +/* + * Public API enums for RTSP requests + */ +enum { + CURL_RTSPREQ_NONE, /* first in list */ + CURL_RTSPREQ_OPTIONS, + CURL_RTSPREQ_DESCRIBE, + CURL_RTSPREQ_ANNOUNCE, + CURL_RTSPREQ_SETUP, + CURL_RTSPREQ_PLAY, + CURL_RTSPREQ_PAUSE, + CURL_RTSPREQ_TEARDOWN, + CURL_RTSPREQ_GET_PARAMETER, + CURL_RTSPREQ_SET_PARAMETER, + CURL_RTSPREQ_RECORD, + CURL_RTSPREQ_RECEIVE, + CURL_RTSPREQ_LAST /* last in list */ +}; + + /* These enums are for use with the CURLOPT_NETRC option. */ +enum CURL_NETRC_OPTION { + CURL_NETRC_IGNORED, /* The .netrc will never be read. + * This is the default. */ + CURL_NETRC_OPTIONAL, /* A user:password in the URL will be preferred + * to one in the .netrc. */ + CURL_NETRC_REQUIRED, /* A user:password in the URL will be ignored. + * Unless one is set programmatically, the .netrc + * will be queried. */ + CURL_NETRC_LAST +}; + +enum { + CURL_SSLVERSION_DEFAULT, + CURL_SSLVERSION_TLSv1, /* TLS 1.x */ + CURL_SSLVERSION_SSLv2, + CURL_SSLVERSION_SSLv3, + CURL_SSLVERSION_TLSv1_0, + CURL_SSLVERSION_TLSv1_1, + CURL_SSLVERSION_TLSv1_2, + + CURL_SSLVERSION_LAST /* never use, keep last */ +}; + +enum CURL_TLSAUTH { + CURL_TLSAUTH_NONE, + CURL_TLSAUTH_SRP, + CURL_TLSAUTH_LAST /* never use, keep last */ +}; + +/* symbols to use with CURLOPT_POSTREDIR. + CURL_REDIR_POST_301, CURL_REDIR_POST_302 and CURL_REDIR_POST_303 + can be bitwise ORed so that CURL_REDIR_POST_301 | CURL_REDIR_POST_302 + | CURL_REDIR_POST_303 == CURL_REDIR_POST_ALL */ + +#define CURL_REDIR_GET_ALL 0 +#define CURL_REDIR_POST_301 1 +#define CURL_REDIR_POST_302 2 +#define CURL_REDIR_POST_303 4 +#define CURL_REDIR_POST_ALL \ + (CURL_REDIR_POST_301|CURL_REDIR_POST_302|CURL_REDIR_POST_303) + +typedef enum { + CURL_TIMECOND_NONE, + + CURL_TIMECOND_IFMODSINCE, + CURL_TIMECOND_IFUNMODSINCE, + CURL_TIMECOND_LASTMOD, + + CURL_TIMECOND_LAST +} curl_TimeCond; + + +/* curl_strequal() and curl_strnequal() are subject for removal in a future + libcurl, see lib/README.curlx for details */ +CURL_EXTERN int (curl_strequal)(const char *s1, const char *s2); +CURL_EXTERN int (curl_strnequal)(const char *s1, const char *s2, size_t n); + +/* name is uppercase CURLFORM_ */ +#ifdef CFINIT +#undef CFINIT +#endif + +#ifdef CURL_ISOCPP +#define CFINIT(name) CURLFORM_ ## name +#else +/* The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */ +#define CFINIT(name) CURLFORM_/**/name +#endif + +typedef enum { + CFINIT(NOTHING), /********* the first one is unused ************/ + + /* */ + CFINIT(COPYNAME), + CFINIT(PTRNAME), + CFINIT(NAMELENGTH), + CFINIT(COPYCONTENTS), + CFINIT(PTRCONTENTS), + CFINIT(CONTENTSLENGTH), + CFINIT(FILECONTENT), + CFINIT(ARRAY), + CFINIT(OBSOLETE), + CFINIT(FILE), + + CFINIT(BUFFER), + CFINIT(BUFFERPTR), + CFINIT(BUFFERLENGTH), + + CFINIT(CONTENTTYPE), + CFINIT(CONTENTHEADER), + CFINIT(FILENAME), + CFINIT(END), + CFINIT(OBSOLETE2), + + CFINIT(STREAM), + + CURLFORM_LASTENTRY /* the last unused */ +} CURLformoption; + +#undef CFINIT /* done */ + +/* structure to be used as parameter for CURLFORM_ARRAY */ +struct curl_forms { + CURLformoption option; + const char *value; +}; + +/* use this for multipart formpost building */ +/* Returns code for curl_formadd() + * + * Returns: + * CURL_FORMADD_OK on success + * CURL_FORMADD_MEMORY if the FormInfo allocation fails + * CURL_FORMADD_OPTION_TWICE if one option is given twice for one Form + * CURL_FORMADD_NULL if a null pointer was given for a char + * CURL_FORMADD_MEMORY if the allocation of a FormInfo struct failed + * CURL_FORMADD_UNKNOWN_OPTION if an unknown option was used + * CURL_FORMADD_INCOMPLETE if the some FormInfo is not complete (or error) + * CURL_FORMADD_MEMORY if a curl_httppost struct cannot be allocated + * CURL_FORMADD_MEMORY if some allocation for string copying failed. + * CURL_FORMADD_ILLEGAL_ARRAY if an illegal option is used in an array + * + ***************************************************************************/ +typedef enum { + CURL_FORMADD_OK, /* first, no error */ + + CURL_FORMADD_MEMORY, + CURL_FORMADD_OPTION_TWICE, + CURL_FORMADD_NULL, + CURL_FORMADD_UNKNOWN_OPTION, + CURL_FORMADD_INCOMPLETE, + CURL_FORMADD_ILLEGAL_ARRAY, + CURL_FORMADD_DISABLED, /* libcurl was built with this disabled */ + + CURL_FORMADD_LAST /* last */ +} CURLFORMcode; + +/* + * NAME curl_formadd() + * + * DESCRIPTION + * + * Pretty advanced function for building multi-part formposts. Each invoke + * adds one part that together construct a full post. Then use + * CURLOPT_HTTPPOST to send it off to libcurl. + */ +CURL_EXTERN CURLFORMcode curl_formadd(struct curl_httppost **httppost, + struct curl_httppost **last_post, + ...); + +/* + * callback function for curl_formget() + * The void *arg pointer will be the one passed as second argument to + * curl_formget(). + * The character buffer passed to it must not be freed. + * Should return the buffer length passed to it as the argument "len" on + * success. + */ +typedef size_t (*curl_formget_callback)(void *arg, const char *buf, + size_t len); + +/* + * NAME curl_formget() + * + * DESCRIPTION + * + * Serialize a curl_httppost struct built with curl_formadd(). + * Accepts a void pointer as second argument which will be passed to + * the curl_formget_callback function. + * Returns 0 on success. + */ +CURL_EXTERN int curl_formget(struct curl_httppost *form, void *arg, + curl_formget_callback append); +/* + * NAME curl_formfree() + * + * DESCRIPTION + * + * Free a multipart formpost previously built with curl_formadd(). + */ +CURL_EXTERN void curl_formfree(struct curl_httppost *form); + +/* + * NAME curl_getenv() + * + * DESCRIPTION + * + * Returns a malloc()'ed string that MUST be curl_free()ed after usage is + * complete. DEPRECATED - see lib/README.curlx + */ +CURL_EXTERN char *curl_getenv(const char *variable); + +/* + * NAME curl_version() + * + * DESCRIPTION + * + * Returns a static ascii string of the libcurl version. + */ +CURL_EXTERN char *curl_version(void); + +/* + * NAME curl_easy_escape() + * + * DESCRIPTION + * + * Escapes URL strings (converts all letters consider illegal in URLs to their + * %XX versions). This function returns a new allocated string or NULL if an + * error occurred. + */ +CURL_EXTERN char *curl_easy_escape(CURL *handle, + const char *string, + int length); + +/* the previous version: */ +CURL_EXTERN char *curl_escape(const char *string, + int length); + + +/* + * NAME curl_easy_unescape() + * + * DESCRIPTION + * + * Unescapes URL encoding in strings (converts all %XX codes to their 8bit + * versions). This function returns a new allocated string or NULL if an error + * occurred. + * Conversion Note: On non-ASCII platforms the ASCII %XX codes are + * converted into the host encoding. + */ +CURL_EXTERN char *curl_easy_unescape(CURL *handle, + const char *string, + int length, + int *outlength); + +/* the previous version */ +CURL_EXTERN char *curl_unescape(const char *string, + int length); + +/* + * NAME curl_free() + * + * DESCRIPTION + * + * Provided for de-allocation in the same translation unit that did the + * allocation. Added in libcurl 7.10 + */ +CURL_EXTERN void curl_free(void *p); + +/* + * NAME curl_global_init() + * + * DESCRIPTION + * + * curl_global_init() should be invoked exactly once for each application that + * uses libcurl and before any call of other libcurl functions. + * + * This function is not thread-safe! + */ +CURL_EXTERN CURLcode curl_global_init(long flags); + +/* + * NAME curl_global_init_mem() + * + * DESCRIPTION + * + * curl_global_init() or curl_global_init_mem() should be invoked exactly once + * for each application that uses libcurl. This function can be used to + * initialize libcurl and set user defined memory management callback + * functions. Users can implement memory management routines to check for + * memory leaks, check for mis-use of the curl library etc. User registered + * callback routines with be invoked by this library instead of the system + * memory management routines like malloc, free etc. + */ +CURL_EXTERN CURLcode curl_global_init_mem(long flags, + curl_malloc_callback m, + curl_free_callback f, + curl_realloc_callback r, + curl_strdup_callback s, + curl_calloc_callback c); + +/* + * NAME curl_global_cleanup() + * + * DESCRIPTION + * + * curl_global_cleanup() should be invoked exactly once for each application + * that uses libcurl + */ +CURL_EXTERN void curl_global_cleanup(void); + +/* linked-list structure for the CURLOPT_QUOTE option (and other) */ +struct curl_slist { + char *data; + struct curl_slist *next; +}; + +/* + * NAME curl_slist_append() + * + * DESCRIPTION + * + * Appends a string to a linked list. If no list exists, it will be created + * first. Returns the new list, after appending. + */ +CURL_EXTERN struct curl_slist *curl_slist_append(struct curl_slist *, + const char *); + +/* + * NAME curl_slist_free_all() + * + * DESCRIPTION + * + * free a previously built curl_slist. + */ +CURL_EXTERN void curl_slist_free_all(struct curl_slist *); + +/* + * NAME curl_getdate() + * + * DESCRIPTION + * + * Returns the time, in seconds since 1 Jan 1970 of the time string given in + * the first argument. The time argument in the second parameter is unused + * and should be set to NULL. + */ +CURL_EXTERN time_t curl_getdate(const char *p, const time_t *unused); + +/* info about the certificate chain, only for OpenSSL builds. Asked + for with CURLOPT_CERTINFO / CURLINFO_CERTINFO */ +struct curl_certinfo { + int num_of_certs; /* number of certificates with information */ + struct curl_slist **certinfo; /* for each index in this array, there's a + linked list with textual information in the + format "name: value" */ +}; + +/* enum for the different supported SSL backends */ +typedef enum { + CURLSSLBACKEND_NONE = 0, + CURLSSLBACKEND_OPENSSL = 1, + CURLSSLBACKEND_GNUTLS = 2, + CURLSSLBACKEND_NSS = 3, + CURLSSLBACKEND_QSOSSL = 4, + CURLSSLBACKEND_GSKIT = 5, + CURLSSLBACKEND_POLARSSL = 6, + CURLSSLBACKEND_CYASSL = 7, + CURLSSLBACKEND_SCHANNEL = 8, + CURLSSLBACKEND_DARWINSSL = 9 +} curl_sslbackend; + +/* Information about the SSL library used and the respective internal SSL + handle, which can be used to obtain further information regarding the + connection. Asked for with CURLINFO_TLS_SESSION. */ +struct curl_tlssessioninfo { + curl_sslbackend backend; + void *internals; +}; + +#define CURLINFO_STRING 0x100000 +#define CURLINFO_LONG 0x200000 +#define CURLINFO_DOUBLE 0x300000 +#define CURLINFO_SLIST 0x400000 +#define CURLINFO_MASK 0x0fffff +#define CURLINFO_TYPEMASK 0xf00000 + +typedef enum { + CURLINFO_NONE, /* first, never use this */ + CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1, + CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2, + CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE + 3, + CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4, + CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5, + CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6, + CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7, + CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8, + CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9, + CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10, + CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11, + CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12, + CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13, + CURLINFO_FILETIME = CURLINFO_LONG + 14, + CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15, + CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16, + CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17, + CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18, + CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19, + CURLINFO_REDIRECT_COUNT = CURLINFO_LONG + 20, + CURLINFO_PRIVATE = CURLINFO_STRING + 21, + CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG + 22, + CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG + 23, + CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG + 24, + CURLINFO_OS_ERRNO = CURLINFO_LONG + 25, + CURLINFO_NUM_CONNECTS = CURLINFO_LONG + 26, + CURLINFO_SSL_ENGINES = CURLINFO_SLIST + 27, + CURLINFO_COOKIELIST = CURLINFO_SLIST + 28, + CURLINFO_LASTSOCKET = CURLINFO_LONG + 29, + CURLINFO_FTP_ENTRY_PATH = CURLINFO_STRING + 30, + CURLINFO_REDIRECT_URL = CURLINFO_STRING + 31, + CURLINFO_PRIMARY_IP = CURLINFO_STRING + 32, + CURLINFO_APPCONNECT_TIME = CURLINFO_DOUBLE + 33, + CURLINFO_CERTINFO = CURLINFO_SLIST + 34, + CURLINFO_CONDITION_UNMET = CURLINFO_LONG + 35, + CURLINFO_RTSP_SESSION_ID = CURLINFO_STRING + 36, + CURLINFO_RTSP_CLIENT_CSEQ = CURLINFO_LONG + 37, + CURLINFO_RTSP_SERVER_CSEQ = CURLINFO_LONG + 38, + CURLINFO_RTSP_CSEQ_RECV = CURLINFO_LONG + 39, + CURLINFO_PRIMARY_PORT = CURLINFO_LONG + 40, + CURLINFO_LOCAL_IP = CURLINFO_STRING + 41, + CURLINFO_LOCAL_PORT = CURLINFO_LONG + 42, + CURLINFO_TLS_SESSION = CURLINFO_SLIST + 43, + /* Fill in new entries below here! */ + + CURLINFO_LASTONE = 43 +} CURLINFO; + +/* CURLINFO_RESPONSE_CODE is the new name for the option previously known as + CURLINFO_HTTP_CODE */ +#define CURLINFO_HTTP_CODE CURLINFO_RESPONSE_CODE + +typedef enum { + CURLCLOSEPOLICY_NONE, /* first, never use this */ + + CURLCLOSEPOLICY_OLDEST, + CURLCLOSEPOLICY_LEAST_RECENTLY_USED, + CURLCLOSEPOLICY_LEAST_TRAFFIC, + CURLCLOSEPOLICY_SLOWEST, + CURLCLOSEPOLICY_CALLBACK, + + CURLCLOSEPOLICY_LAST /* last, never use this */ +} curl_closepolicy; + +#define CURL_GLOBAL_SSL (1<<0) +#define CURL_GLOBAL_WIN32 (1<<1) +#define CURL_GLOBAL_ALL (CURL_GLOBAL_SSL|CURL_GLOBAL_WIN32) +#define CURL_GLOBAL_NOTHING 0 +#define CURL_GLOBAL_DEFAULT CURL_GLOBAL_ALL +#define CURL_GLOBAL_ACK_EINTR (1<<2) + + +/***************************************************************************** + * Setup defines, protos etc for the sharing stuff. + */ + +/* Different data locks for a single share */ +typedef enum { + CURL_LOCK_DATA_NONE = 0, + /* CURL_LOCK_DATA_SHARE is used internally to say that + * the locking is just made to change the internal state of the share + * itself. + */ + CURL_LOCK_DATA_SHARE, + CURL_LOCK_DATA_COOKIE, + CURL_LOCK_DATA_DNS, + CURL_LOCK_DATA_SSL_SESSION, + CURL_LOCK_DATA_CONNECT, + CURL_LOCK_DATA_LAST +} curl_lock_data; + +/* Different lock access types */ +typedef enum { + CURL_LOCK_ACCESS_NONE = 0, /* unspecified action */ + CURL_LOCK_ACCESS_SHARED = 1, /* for read perhaps */ + CURL_LOCK_ACCESS_SINGLE = 2, /* for write perhaps */ + CURL_LOCK_ACCESS_LAST /* never use */ +} curl_lock_access; + +typedef void (*curl_lock_function)(CURL *handle, + curl_lock_data data, + curl_lock_access locktype, + void *userptr); +typedef void (*curl_unlock_function)(CURL *handle, + curl_lock_data data, + void *userptr); + +typedef void CURLSH; + +typedef enum { + CURLSHE_OK, /* all is fine */ + CURLSHE_BAD_OPTION, /* 1 */ + CURLSHE_IN_USE, /* 2 */ + CURLSHE_INVALID, /* 3 */ + CURLSHE_NOMEM, /* 4 out of memory */ + CURLSHE_NOT_BUILT_IN, /* 5 feature not present in lib */ + CURLSHE_LAST /* never use */ +} CURLSHcode; + +typedef enum { + CURLSHOPT_NONE, /* don't use */ + CURLSHOPT_SHARE, /* specify a data type to share */ + CURLSHOPT_UNSHARE, /* specify which data type to stop sharing */ + CURLSHOPT_LOCKFUNC, /* pass in a 'curl_lock_function' pointer */ + CURLSHOPT_UNLOCKFUNC, /* pass in a 'curl_unlock_function' pointer */ + CURLSHOPT_USERDATA, /* pass in a user data pointer used in the lock/unlock + callback functions */ + CURLSHOPT_LAST /* never use */ +} CURLSHoption; + +CURL_EXTERN CURLSH *curl_share_init(void); +CURL_EXTERN CURLSHcode curl_share_setopt(CURLSH *, CURLSHoption option, ...); +CURL_EXTERN CURLSHcode curl_share_cleanup(CURLSH *); + +/**************************************************************************** + * Structures for querying information about the curl library at runtime. + */ + +typedef enum { + CURLVERSION_FIRST, + CURLVERSION_SECOND, + CURLVERSION_THIRD, + CURLVERSION_FOURTH, + CURLVERSION_LAST /* never actually use this */ +} CURLversion; + +/* The 'CURLVERSION_NOW' is the symbolic name meant to be used by + basically all programs ever that want to get version information. It is + meant to be a built-in version number for what kind of struct the caller + expects. If the struct ever changes, we redefine the NOW to another enum + from above. */ +#define CURLVERSION_NOW CURLVERSION_FOURTH + +typedef struct { + CURLversion age; /* age of the returned struct */ + const char *version; /* LIBCURL_VERSION */ + unsigned int version_num; /* LIBCURL_VERSION_NUM */ + const char *host; /* OS/host/cpu/machine when configured */ + int features; /* bitmask, see defines below */ + const char *ssl_version; /* human readable string */ + long ssl_version_num; /* not used anymore, always 0 */ + const char *libz_version; /* human readable string */ + /* protocols is terminated by an entry with a NULL protoname */ + const char * const *protocols; + + /* The fields below this were added in CURLVERSION_SECOND */ + const char *ares; + int ares_num; + + /* This field was added in CURLVERSION_THIRD */ + const char *libidn; + + /* These field were added in CURLVERSION_FOURTH */ + + /* Same as '_libiconv_version' if built with HAVE_ICONV */ + int iconv_ver_num; + + const char *libssh_version; /* human readable string */ + +} curl_version_info_data; + +#define CURL_VERSION_IPV6 (1<<0) /* IPv6-enabled */ +#define CURL_VERSION_KERBEROS4 (1<<1) /* kerberos auth is supported */ +#define CURL_VERSION_SSL (1<<2) /* SSL options are present */ +#define CURL_VERSION_LIBZ (1<<3) /* libz features are present */ +#define CURL_VERSION_NTLM (1<<4) /* NTLM auth is supported */ +#define CURL_VERSION_GSSNEGOTIATE (1<<5) /* Negotiate auth support */ +#define CURL_VERSION_DEBUG (1<<6) /* built with debug capabilities */ +#define CURL_VERSION_ASYNCHDNS (1<<7) /* asynchronous dns resolves */ +#define CURL_VERSION_SPNEGO (1<<8) /* SPNEGO auth */ +#define CURL_VERSION_LARGEFILE (1<<9) /* supports files bigger than 2GB */ +#define CURL_VERSION_IDN (1<<10) /* International Domain Names support */ +#define CURL_VERSION_SSPI (1<<11) /* SSPI is supported */ +#define CURL_VERSION_CONV (1<<12) /* character conversions supported */ +#define CURL_VERSION_CURLDEBUG (1<<13) /* debug memory tracking supported */ +#define CURL_VERSION_TLSAUTH_SRP (1<<14) /* TLS-SRP auth is supported */ +#define CURL_VERSION_NTLM_WB (1<<15) /* NTLM delegating to winbind helper */ +#define CURL_VERSION_HTTP2 (1<<16) /* HTTP2 support built-in */ + + /* + * NAME curl_version_info() + * + * DESCRIPTION + * + * This function returns a pointer to a static copy of the version info + * struct. See above. + */ +CURL_EXTERN curl_version_info_data *curl_version_info(CURLversion); + +/* + * NAME curl_easy_strerror() + * + * DESCRIPTION + * + * The curl_easy_strerror function may be used to turn a CURLcode value + * into the equivalent human readable error string. This is useful + * for printing meaningful error messages. + */ +CURL_EXTERN const char *curl_easy_strerror(CURLcode); + +/* + * NAME curl_share_strerror() + * + * DESCRIPTION + * + * The curl_share_strerror function may be used to turn a CURLSHcode value + * into the equivalent human readable error string. This is useful + * for printing meaningful error messages. + */ +CURL_EXTERN const char *curl_share_strerror(CURLSHcode); + +/* + * NAME curl_easy_pause() + * + * DESCRIPTION + * + * The curl_easy_pause function pauses or unpauses transfers. Select the new + * state by setting the bitmask, use the convenience defines below. + * + */ +CURL_EXTERN CURLcode curl_easy_pause(CURL *handle, int bitmask); + +#define CURLPAUSE_RECV (1<<0) +#define CURLPAUSE_RECV_CONT (0) + +#define CURLPAUSE_SEND (1<<2) +#define CURLPAUSE_SEND_CONT (0) + +#define CURLPAUSE_ALL (CURLPAUSE_RECV|CURLPAUSE_SEND) +#define CURLPAUSE_CONT (CURLPAUSE_RECV_CONT|CURLPAUSE_SEND_CONT) + +#ifdef __cplusplus +} +#endif + +/* unfortunately, the easy.h and multi.h include files need options and info + stuff before they can be included! */ +#include "easy.h" /* nothing in curl is fun without the easy stuff */ +#include "multi.h" + +/* the typechecker doesn't work in C++ (yet) */ +#if defined(__GNUC__) && defined(__GNUC_MINOR__) && \ + ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) && \ + !defined(__cplusplus) && !defined(CURL_DISABLE_TYPECHECK) +#include "typecheck-gcc.h" +#else +#if defined(__STDC__) && (__STDC__ >= 1) +/* This preprocessor magic that replaces a call with the exact same call is + only done to make sure application authors pass exactly three arguments + to these functions. */ +#define curl_easy_setopt(handle,opt,param) curl_easy_setopt(handle,opt,param) +#define curl_easy_getinfo(handle,info,arg) curl_easy_getinfo(handle,info,arg) +#define curl_share_setopt(share,opt,param) curl_share_setopt(share,opt,param) +#define curl_multi_setopt(handle,opt,param) curl_multi_setopt(handle,opt,param) +#endif /* __STDC__ >= 1 */ +#endif /* gcc >= 4.3 && !__cplusplus */ + +#endif /* __CURL_CURL_H */ diff --git a/deps/src/curl/curlbuild.h b/deps/src/curl/curlbuild.h new file mode 100644 index 000000000..3ad2f018d --- /dev/null +++ b/deps/src/curl/curlbuild.h @@ -0,0 +1,585 @@ +#ifndef __CURL_CURLBUILD_H +#define __CURL_CURLBUILD_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/* ================================================================ */ +/* NOTES FOR CONFIGURE CAPABLE SYSTEMS */ +/* ================================================================ */ + +/* + * NOTE 1: + * ------- + * + * See file include/curl/curlbuild.h.in, run configure, and forget + * that this file exists it is only used for non-configure systems. + * But you can keep reading if you want ;-) + * + */ + +/* ================================================================ */ +/* NOTES FOR NON-CONFIGURE SYSTEMS */ +/* ================================================================ */ + +/* + * NOTE 1: + * ------- + * + * Nothing in this file is intended to be modified or adjusted by the + * curl library user nor by the curl library builder. + * + * If you think that something actually needs to be changed, adjusted + * or fixed in this file, then, report it on the libcurl development + * mailing list: http://cool.haxx.se/mailman/listinfo/curl-library/ + * + * Try to keep one section per platform, compiler and architecture, + * otherwise, if an existing section is reused for a different one and + * later on the original is adjusted, probably the piggybacking one can + * be adversely changed. + * + * In order to differentiate between platforms/compilers/architectures + * use only compiler built in predefined preprocessor symbols. + * + * This header file shall only export symbols which are 'curl' or 'CURL' + * prefixed, otherwise public name space would be polluted. + * + * NOTE 2: + * ------- + * + * For any given platform/compiler curl_off_t must be typedef'ed to a + * 64-bit wide signed integral data type. The width of this data type + * must remain constant and independent of any possible large file + * support settings. + * + * As an exception to the above, curl_off_t shall be typedef'ed to a + * 32-bit wide signed integral data type if there is no 64-bit type. + * + * As a general rule, curl_off_t shall not be mapped to off_t. This + * rule shall only be violated if off_t is the only 64-bit data type + * available and the size of off_t is independent of large file support + * settings. Keep your build on the safe side avoiding an off_t gating. + * If you have a 64-bit off_t then take for sure that another 64-bit + * data type exists, dig deeper and you will find it. + * + * NOTE 3: + * ------- + * + * Right now you might be staring at file include/curl/curlbuild.h.dist or + * at file include/curl/curlbuild.h, this is due to the following reason: + * file include/curl/curlbuild.h.dist is renamed to include/curl/curlbuild.h + * when the libcurl source code distribution archive file is created. + * + * File include/curl/curlbuild.h.dist is not included in the distribution + * archive. File include/curl/curlbuild.h is not present in the git tree. + * + * The distributed include/curl/curlbuild.h file is only intended to be used + * on systems which can not run the also distributed configure script. + * + * On systems capable of running the configure script, the configure process + * will overwrite the distributed include/curl/curlbuild.h file with one that + * is suitable and specific to the library being configured and built, which + * is generated from the include/curl/curlbuild.h.in template file. + * + * If you check out from git on a non-configure platform, you must run the + * appropriate buildconf* script to set up curlbuild.h and other local files. + * + */ + +/* ================================================================ */ +/* DEFINITION OF THESE SYMBOLS SHALL NOT TAKE PLACE ANYWHERE ELSE */ +/* ================================================================ */ + +#ifdef CURL_SIZEOF_LONG +# error "CURL_SIZEOF_LONG shall not be defined except in curlbuild.h" + Error Compilation_aborted_CURL_SIZEOF_LONG_already_defined +#endif + +#ifdef CURL_TYPEOF_CURL_SOCKLEN_T +# error "CURL_TYPEOF_CURL_SOCKLEN_T shall not be defined except in curlbuild.h" + Error Compilation_aborted_CURL_TYPEOF_CURL_SOCKLEN_T_already_defined +#endif + +#ifdef CURL_SIZEOF_CURL_SOCKLEN_T +# error "CURL_SIZEOF_CURL_SOCKLEN_T shall not be defined except in curlbuild.h" + Error Compilation_aborted_CURL_SIZEOF_CURL_SOCKLEN_T_already_defined +#endif + +#ifdef CURL_TYPEOF_CURL_OFF_T +# error "CURL_TYPEOF_CURL_OFF_T shall not be defined except in curlbuild.h" + Error Compilation_aborted_CURL_TYPEOF_CURL_OFF_T_already_defined +#endif + +#ifdef CURL_FORMAT_CURL_OFF_T +# error "CURL_FORMAT_CURL_OFF_T shall not be defined except in curlbuild.h" + Error Compilation_aborted_CURL_FORMAT_CURL_OFF_T_already_defined +#endif + +#ifdef CURL_FORMAT_CURL_OFF_TU +# error "CURL_FORMAT_CURL_OFF_TU shall not be defined except in curlbuild.h" + Error Compilation_aborted_CURL_FORMAT_CURL_OFF_TU_already_defined +#endif + +#ifdef CURL_FORMAT_OFF_T +# error "CURL_FORMAT_OFF_T shall not be defined except in curlbuild.h" + Error Compilation_aborted_CURL_FORMAT_OFF_T_already_defined +#endif + +#ifdef CURL_SIZEOF_CURL_OFF_T +# error "CURL_SIZEOF_CURL_OFF_T shall not be defined except in curlbuild.h" + Error Compilation_aborted_CURL_SIZEOF_CURL_OFF_T_already_defined +#endif + +#ifdef CURL_SUFFIX_CURL_OFF_T +# error "CURL_SUFFIX_CURL_OFF_T shall not be defined except in curlbuild.h" + Error Compilation_aborted_CURL_SUFFIX_CURL_OFF_T_already_defined +#endif + +#ifdef CURL_SUFFIX_CURL_OFF_TU +# error "CURL_SUFFIX_CURL_OFF_TU shall not be defined except in curlbuild.h" + Error Compilation_aborted_CURL_SUFFIX_CURL_OFF_TU_already_defined +#endif + +/* ================================================================ */ +/* EXTERNAL INTERFACE SETTINGS FOR NON-CONFIGURE SYSTEMS ONLY */ +/* ================================================================ */ + +#if defined(__DJGPP__) || defined(__GO32__) +# if defined(__DJGPP__) && (__DJGPP__ > 1) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_FORMAT_OFF_T "%lld" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# else +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_FORMAT_OFF_T "%ld" +# define CURL_SIZEOF_CURL_OFF_T 4 +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 + +#elif defined(__SALFORDC__) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_FORMAT_OFF_T "%ld" +# define CURL_SIZEOF_CURL_OFF_T 4 +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 + +#elif defined(__BORLANDC__) +# if (__BORLANDC__ < 0x520) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_FORMAT_OFF_T "%ld" +# define CURL_SIZEOF_CURL_OFF_T 4 +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# else +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_FORMAT_CURL_OFF_T "I64d" +# define CURL_FORMAT_CURL_OFF_TU "I64u" +# define CURL_FORMAT_OFF_T "%I64d" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T i64 +# define CURL_SUFFIX_CURL_OFF_TU ui64 +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 + +#elif defined(__TURBOC__) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_FORMAT_OFF_T "%ld" +# define CURL_SIZEOF_CURL_OFF_T 4 +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 + +#elif defined(__WATCOMC__) +# if defined(__386__) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_FORMAT_CURL_OFF_T "I64d" +# define CURL_FORMAT_CURL_OFF_TU "I64u" +# define CURL_FORMAT_OFF_T "%I64d" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T i64 +# define CURL_SUFFIX_CURL_OFF_TU ui64 +# else +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_FORMAT_OFF_T "%ld" +# define CURL_SIZEOF_CURL_OFF_T 4 +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 + +#elif defined(__POCC__) +# if (__POCC__ < 280) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_FORMAT_OFF_T "%ld" +# define CURL_SIZEOF_CURL_OFF_T 4 +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# elif defined(_MSC_VER) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_FORMAT_CURL_OFF_T "I64d" +# define CURL_FORMAT_CURL_OFF_TU "I64u" +# define CURL_FORMAT_OFF_T "%I64d" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T i64 +# define CURL_SUFFIX_CURL_OFF_TU ui64 +# else +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_FORMAT_OFF_T "%lld" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 + +#elif defined(__LCC__) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_FORMAT_OFF_T "%ld" +# define CURL_SIZEOF_CURL_OFF_T 4 +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 + +#elif defined(__SYMBIAN32__) +# if defined(__EABI__) /* Treat all ARM compilers equally */ +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_FORMAT_OFF_T "%lld" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# elif defined(__CW32__) +# pragma longlong on +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_FORMAT_OFF_T "%lld" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# elif defined(__VC32__) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_FORMAT_OFF_T "%lld" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 + +#elif defined(__MWERKS__) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_FORMAT_OFF_T "%lld" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 + +#elif defined(_WIN32_WCE) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_FORMAT_CURL_OFF_T "I64d" +# define CURL_FORMAT_CURL_OFF_TU "I64u" +# define CURL_FORMAT_OFF_T "%I64d" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T i64 +# define CURL_SUFFIX_CURL_OFF_TU ui64 +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 + +#elif defined(__MINGW32__) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "I64d" +# define CURL_FORMAT_CURL_OFF_TU "I64u" +# define CURL_FORMAT_OFF_T "%I64d" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 + +#elif defined(__VMS) +# if defined(__VAX) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_FORMAT_OFF_T "%ld" +# define CURL_SIZEOF_CURL_OFF_T 4 +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# else +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_FORMAT_OFF_T "%lld" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 + +#elif defined(__OS400__) +# if defined(__ILEC400__) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_FORMAT_OFF_T "%lld" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 +# endif + +#elif defined(__MVS__) +# if defined(__IBMC__) || defined(__IBMCPP__) +# if defined(_ILP32) +# define CURL_SIZEOF_LONG 4 +# elif defined(_LP64) +# define CURL_SIZEOF_LONG 8 +# endif +# if defined(_LONG_LONG) +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_FORMAT_OFF_T "%lld" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# elif defined(_LP64) +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_FORMAT_OFF_T "%ld" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# else +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_FORMAT_OFF_T "%ld" +# define CURL_SIZEOF_CURL_OFF_T 4 +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 +# endif + +#elif defined(__370__) +# if defined(__IBMC__) || defined(__IBMCPP__) +# if defined(_ILP32) +# define CURL_SIZEOF_LONG 4 +# elif defined(_LP64) +# define CURL_SIZEOF_LONG 8 +# endif +# if defined(_LONG_LONG) +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_FORMAT_OFF_T "%lld" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# elif defined(_LP64) +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_FORMAT_OFF_T "%ld" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# else +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_FORMAT_OFF_T "%ld" +# define CURL_SIZEOF_CURL_OFF_T 4 +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 +# endif + +#elif defined(TPF) +# define CURL_SIZEOF_LONG 8 +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_FORMAT_OFF_T "%ld" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 + +/* ===================================== */ +/* KEEP MSVC THE PENULTIMATE ENTRY */ +/* ===================================== */ + +#elif defined(_MSC_VER) +# if (_MSC_VER >= 900) && (_INTEGRAL_MAX_BITS >= 64) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T __int64 +# define CURL_FORMAT_CURL_OFF_T "I64d" +# define CURL_FORMAT_CURL_OFF_TU "I64u" +# define CURL_FORMAT_OFF_T "%I64d" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T i64 +# define CURL_SUFFIX_CURL_OFF_TU ui64 +# else +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_FORMAT_OFF_T "%ld" +# define CURL_SIZEOF_CURL_OFF_T 4 +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T int +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 + +/* ===================================== */ +/* KEEP GENERIC GCC THE LAST ENTRY */ +/* ===================================== */ + +#elif defined(__GNUC__) +# if defined(__ILP32__) || \ + defined(__i386__) || defined(__ppc__) || defined(__arm__) +# define CURL_SIZEOF_LONG 4 +# define CURL_TYPEOF_CURL_OFF_T long long +# define CURL_FORMAT_CURL_OFF_T "lld" +# define CURL_FORMAT_CURL_OFF_TU "llu" +# define CURL_FORMAT_OFF_T "%lld" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T LL +# define CURL_SUFFIX_CURL_OFF_TU ULL +# elif defined(__LP64__) || \ + defined(__x86_64__) || defined(__ppc64__) +# define CURL_SIZEOF_LONG 8 +# define CURL_TYPEOF_CURL_OFF_T long +# define CURL_FORMAT_CURL_OFF_T "ld" +# define CURL_FORMAT_CURL_OFF_TU "lu" +# define CURL_FORMAT_OFF_T "%ld" +# define CURL_SIZEOF_CURL_OFF_T 8 +# define CURL_SUFFIX_CURL_OFF_T L +# define CURL_SUFFIX_CURL_OFF_TU UL +# endif +# define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t +# define CURL_SIZEOF_CURL_SOCKLEN_T 4 +# define CURL_PULL_SYS_TYPES_H 1 +# define CURL_PULL_SYS_SOCKET_H 1 + +#else +# error "Unknown non-configure build target!" + Error Compilation_aborted_Unknown_non_configure_build_target +#endif + +/* CURL_PULL_SYS_TYPES_H is defined above when inclusion of header file */ +/* sys/types.h is required here to properly make type definitions below. */ +#ifdef CURL_PULL_SYS_TYPES_H +# include +#endif + +/* CURL_PULL_SYS_SOCKET_H is defined above when inclusion of header file */ +/* sys/socket.h is required here to properly make type definitions below. */ +#ifdef CURL_PULL_SYS_SOCKET_H +# include +#endif + +/* Data type definition of curl_socklen_t. */ + +#ifdef CURL_TYPEOF_CURL_SOCKLEN_T + typedef CURL_TYPEOF_CURL_SOCKLEN_T curl_socklen_t; +#endif + +/* Data type definition of curl_off_t. */ + +#ifdef CURL_TYPEOF_CURL_OFF_T + typedef CURL_TYPEOF_CURL_OFF_T curl_off_t; +#endif + +#endif /* __CURL_CURLBUILD_H */ diff --git a/deps/src/curl/curlrules.h b/deps/src/curl/curlrules.h new file mode 100644 index 000000000..7c2ede35b --- /dev/null +++ b/deps/src/curl/curlrules.h @@ -0,0 +1,262 @@ +#ifndef __CURL_CURLRULES_H +#define __CURL_CURLRULES_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2012, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/* ================================================================ */ +/* COMPILE TIME SANITY CHECKS */ +/* ================================================================ */ + +/* + * NOTE 1: + * ------- + * + * All checks done in this file are intentionally placed in a public + * header file which is pulled by curl/curl.h when an application is + * being built using an already built libcurl library. Additionally + * this file is also included and used when building the library. + * + * If compilation fails on this file it is certainly sure that the + * problem is elsewhere. It could be a problem in the curlbuild.h + * header file, or simply that you are using different compilation + * settings than those used to build the library. + * + * Nothing in this file is intended to be modified or adjusted by the + * curl library user nor by the curl library builder. + * + * Do not deactivate any check, these are done to make sure that the + * library is properly built and used. + * + * You can find further help on the libcurl development mailing list: + * http://cool.haxx.se/mailman/listinfo/curl-library/ + * + * NOTE 2 + * ------ + * + * Some of the following compile time checks are based on the fact + * that the dimension of a constant array can not be a negative one. + * In this way if the compile time verification fails, the compilation + * will fail issuing an error. The error description wording is compiler + * dependent but it will be quite similar to one of the following: + * + * "negative subscript or subscript is too large" + * "array must have at least one element" + * "-1 is an illegal array size" + * "size of array is negative" + * + * If you are building an application which tries to use an already + * built libcurl library and you are getting this kind of errors on + * this file, it is a clear indication that there is a mismatch between + * how the library was built and how you are trying to use it for your + * application. Your already compiled or binary library provider is the + * only one who can give you the details you need to properly use it. + */ + +/* + * Verify that some macros are actually defined. + */ + +#ifndef CURL_SIZEOF_LONG +# error "CURL_SIZEOF_LONG definition is missing!" + Error Compilation_aborted_CURL_SIZEOF_LONG_is_missing +#endif + +#ifndef CURL_TYPEOF_CURL_SOCKLEN_T +# error "CURL_TYPEOF_CURL_SOCKLEN_T definition is missing!" + Error Compilation_aborted_CURL_TYPEOF_CURL_SOCKLEN_T_is_missing +#endif + +#ifndef CURL_SIZEOF_CURL_SOCKLEN_T +# error "CURL_SIZEOF_CURL_SOCKLEN_T definition is missing!" + Error Compilation_aborted_CURL_SIZEOF_CURL_SOCKLEN_T_is_missing +#endif + +#ifndef CURL_TYPEOF_CURL_OFF_T +# error "CURL_TYPEOF_CURL_OFF_T definition is missing!" + Error Compilation_aborted_CURL_TYPEOF_CURL_OFF_T_is_missing +#endif + +#ifndef CURL_FORMAT_CURL_OFF_T +# error "CURL_FORMAT_CURL_OFF_T definition is missing!" + Error Compilation_aborted_CURL_FORMAT_CURL_OFF_T_is_missing +#endif + +#ifndef CURL_FORMAT_CURL_OFF_TU +# error "CURL_FORMAT_CURL_OFF_TU definition is missing!" + Error Compilation_aborted_CURL_FORMAT_CURL_OFF_TU_is_missing +#endif + +#ifndef CURL_FORMAT_OFF_T +# error "CURL_FORMAT_OFF_T definition is missing!" + Error Compilation_aborted_CURL_FORMAT_OFF_T_is_missing +#endif + +#ifndef CURL_SIZEOF_CURL_OFF_T +# error "CURL_SIZEOF_CURL_OFF_T definition is missing!" + Error Compilation_aborted_CURL_SIZEOF_CURL_OFF_T_is_missing +#endif + +#ifndef CURL_SUFFIX_CURL_OFF_T +# error "CURL_SUFFIX_CURL_OFF_T definition is missing!" + Error Compilation_aborted_CURL_SUFFIX_CURL_OFF_T_is_missing +#endif + +#ifndef CURL_SUFFIX_CURL_OFF_TU +# error "CURL_SUFFIX_CURL_OFF_TU definition is missing!" + Error Compilation_aborted_CURL_SUFFIX_CURL_OFF_TU_is_missing +#endif + +/* + * Macros private to this header file. + */ + +#define CurlchkszEQ(t, s) sizeof(t) == s ? 1 : -1 + +#define CurlchkszGE(t1, t2) sizeof(t1) >= sizeof(t2) ? 1 : -1 + +/* + * Verify that the size previously defined and expected for long + * is the same as the one reported by sizeof() at compile time. + */ + +typedef char + __curl_rule_01__ + [CurlchkszEQ(long, CURL_SIZEOF_LONG)]; + +/* + * Verify that the size previously defined and expected for + * curl_off_t is actually the the same as the one reported + * by sizeof() at compile time. + */ + +typedef char + __curl_rule_02__ + [CurlchkszEQ(curl_off_t, CURL_SIZEOF_CURL_OFF_T)]; + +/* + * Verify at compile time that the size of curl_off_t as reported + * by sizeof() is greater or equal than the one reported for long + * for the current compilation. + */ + +typedef char + __curl_rule_03__ + [CurlchkszGE(curl_off_t, long)]; + +/* + * Verify that the size previously defined and expected for + * curl_socklen_t is actually the the same as the one reported + * by sizeof() at compile time. + */ + +typedef char + __curl_rule_04__ + [CurlchkszEQ(curl_socklen_t, CURL_SIZEOF_CURL_SOCKLEN_T)]; + +/* + * Verify at compile time that the size of curl_socklen_t as reported + * by sizeof() is greater or equal than the one reported for int for + * the current compilation. + */ + +typedef char + __curl_rule_05__ + [CurlchkszGE(curl_socklen_t, int)]; + +/* ================================================================ */ +/* EXTERNALLY AND INTERNALLY VISIBLE DEFINITIONS */ +/* ================================================================ */ + +/* + * CURL_ISOCPP and CURL_OFF_T_C definitions are done here in order to allow + * these to be visible and exported by the external libcurl interface API, + * while also making them visible to the library internals, simply including + * curl_setup.h, without actually needing to include curl.h internally. + * If some day this section would grow big enough, all this should be moved + * to its own header file. + */ + +/* + * Figure out if we can use the ## preprocessor operator, which is supported + * by ISO/ANSI C and C++. Some compilers support it without setting __STDC__ + * or __cplusplus so we need to carefully check for them too. + */ + +#if defined(__STDC__) || defined(_MSC_VER) || defined(__cplusplus) || \ + defined(__HP_aCC) || defined(__BORLANDC__) || defined(__LCC__) || \ + defined(__POCC__) || defined(__SALFORDC__) || defined(__HIGHC__) || \ + defined(__ILEC400__) + /* This compiler is believed to have an ISO compatible preprocessor */ +#define CURL_ISOCPP +#else + /* This compiler is believed NOT to have an ISO compatible preprocessor */ +#undef CURL_ISOCPP +#endif + +/* + * Macros for minimum-width signed and unsigned curl_off_t integer constants. + */ + +#if defined(__BORLANDC__) && (__BORLANDC__ == 0x0551) +# define __CURL_OFF_T_C_HLPR2(x) x +# define __CURL_OFF_T_C_HLPR1(x) __CURL_OFF_T_C_HLPR2(x) +# define CURL_OFF_T_C(Val) __CURL_OFF_T_C_HLPR1(Val) ## \ + __CURL_OFF_T_C_HLPR1(CURL_SUFFIX_CURL_OFF_T) +# define CURL_OFF_TU_C(Val) __CURL_OFF_T_C_HLPR1(Val) ## \ + __CURL_OFF_T_C_HLPR1(CURL_SUFFIX_CURL_OFF_TU) +#else +# ifdef CURL_ISOCPP +# define __CURL_OFF_T_C_HLPR2(Val,Suffix) Val ## Suffix +# else +# define __CURL_OFF_T_C_HLPR2(Val,Suffix) Val/**/Suffix +# endif +# define __CURL_OFF_T_C_HLPR1(Val,Suffix) __CURL_OFF_T_C_HLPR2(Val,Suffix) +# define CURL_OFF_T_C(Val) __CURL_OFF_T_C_HLPR1(Val,CURL_SUFFIX_CURL_OFF_T) +# define CURL_OFF_TU_C(Val) __CURL_OFF_T_C_HLPR1(Val,CURL_SUFFIX_CURL_OFF_TU) +#endif + +/* + * Get rid of macros private to this header file. + */ + +#undef CurlchkszEQ +#undef CurlchkszGE + +/* + * Get rid of macros not intended to exist beyond this point. + */ + +#undef CURL_PULL_WS2TCPIP_H +#undef CURL_PULL_SYS_TYPES_H +#undef CURL_PULL_SYS_SOCKET_H +#undef CURL_PULL_SYS_POLL_H +#undef CURL_PULL_STDINT_H +#undef CURL_PULL_INTTYPES_H + +#undef CURL_TYPEOF_CURL_SOCKLEN_T +#undef CURL_TYPEOF_CURL_OFF_T + +#ifdef CURL_NO_OLDIES +#undef CURL_FORMAT_OFF_T /* not required since 7.19.0 - obsoleted in 7.20.0 */ +#endif + +#endif /* __CURL_CURLRULES_H */ diff --git a/deps/src/curl/curlver.h b/deps/src/curl/curlver.h new file mode 100644 index 000000000..c472dbe41 --- /dev/null +++ b/deps/src/curl/curlver.h @@ -0,0 +1,69 @@ +#ifndef __CURL_CURLVER_H +#define __CURL_CURLVER_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2014, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/* This header file contains nothing but libcurl version info, generated by + a script at release-time. This was made its own header file in 7.11.2 */ + +/* This is the global package copyright */ +#define LIBCURL_COPYRIGHT "1996 - 2014 Daniel Stenberg, ." + +/* This is the version number of the libcurl package from which this header + file origins: */ +#define LIBCURL_VERSION "7.37.0" + +/* The numeric version number is also available "in parts" by using these + defines: */ +#define LIBCURL_VERSION_MAJOR 7 +#define LIBCURL_VERSION_MINOR 37 +#define LIBCURL_VERSION_PATCH 0 + +/* This is the numeric version of the libcurl version number, meant for easier + parsing and comparions by programs. The LIBCURL_VERSION_NUM define will + always follow this syntax: + + 0xXXYYZZ + + Where XX, YY and ZZ are the main version, release and patch numbers in + hexadecimal (using 8 bits each). All three numbers are always represented + using two digits. 1.2 would appear as "0x010200" while version 9.11.7 + appears as "0x090b07". + + This 6-digit (24 bits) hexadecimal number does not show pre-release number, + and it is always a greater number in a more recent release. It makes + comparisons with greater than and less than work. +*/ +#define LIBCURL_VERSION_NUM 0x072500 + +/* + * This is the date and time when the full source package was created. The + * timestamp is not stored in git, as the timestamp is properly set in the + * tarballs by the maketgz script. + * + * The format of the date should follow this template: + * + * "Mon Feb 12 11:35:33 UTC 2007" + */ +#define LIBCURL_TIMESTAMP "Wed May 21 05:58:26 UTC 2014" + +#endif /* __CURL_CURLVER_H */ diff --git a/deps/src/curl/easy.h b/deps/src/curl/easy.h new file mode 100644 index 000000000..c1e3e7609 --- /dev/null +++ b/deps/src/curl/easy.h @@ -0,0 +1,102 @@ +#ifndef __CURL_EASY_H +#define __CURL_EASY_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2008, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ +#ifdef __cplusplus +extern "C" { +#endif + +CURL_EXTERN CURL *curl_easy_init(void); +CURL_EXTERN CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...); +CURL_EXTERN CURLcode curl_easy_perform(CURL *curl); +CURL_EXTERN void curl_easy_cleanup(CURL *curl); + +/* + * NAME curl_easy_getinfo() + * + * DESCRIPTION + * + * Request internal information from the curl session with this function. The + * third argument MUST be a pointer to a long, a pointer to a char * or a + * pointer to a double (as the documentation describes elsewhere). The data + * pointed to will be filled in accordingly and can be relied upon only if the + * function returns CURLE_OK. This function is intended to get used *AFTER* a + * performed transfer, all results from this function are undefined until the + * transfer is completed. + */ +CURL_EXTERN CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...); + + +/* + * NAME curl_easy_duphandle() + * + * DESCRIPTION + * + * Creates a new curl session handle with the same options set for the handle + * passed in. Duplicating a handle could only be a matter of cloning data and + * options, internal state info and things like persistent connections cannot + * be transferred. It is useful in multithreaded applications when you can run + * curl_easy_duphandle() for each new thread to avoid a series of identical + * curl_easy_setopt() invokes in every thread. + */ +CURL_EXTERN CURL* curl_easy_duphandle(CURL *curl); + +/* + * NAME curl_easy_reset() + * + * DESCRIPTION + * + * Re-initializes a CURL handle to the default values. This puts back the + * handle to the same state as it was in when it was just created. + * + * It does keep: live connections, the Session ID cache, the DNS cache and the + * cookies. + */ +CURL_EXTERN void curl_easy_reset(CURL *curl); + +/* + * NAME curl_easy_recv() + * + * DESCRIPTION + * + * Receives data from the connected socket. Use after successful + * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. + */ +CURL_EXTERN CURLcode curl_easy_recv(CURL *curl, void *buffer, size_t buflen, + size_t *n); + +/* + * NAME curl_easy_send() + * + * DESCRIPTION + * + * Sends data over the connected socket. Use after successful + * curl_easy_perform() with CURLOPT_CONNECT_ONLY option. + */ +CURL_EXTERN CURLcode curl_easy_send(CURL *curl, const void *buffer, + size_t buflen, size_t *n); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/deps/src/curl/mprintf.h b/deps/src/curl/mprintf.h new file mode 100644 index 000000000..cc9e7f5d1 --- /dev/null +++ b/deps/src/curl/mprintf.h @@ -0,0 +1,81 @@ +#ifndef __CURL_MPRINTF_H +#define __CURL_MPRINTF_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include +#include /* needed for FILE */ + +#include "curl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +CURL_EXTERN int curl_mprintf(const char *format, ...); +CURL_EXTERN int curl_mfprintf(FILE *fd, const char *format, ...); +CURL_EXTERN int curl_msprintf(char *buffer, const char *format, ...); +CURL_EXTERN int curl_msnprintf(char *buffer, size_t maxlength, + const char *format, ...); +CURL_EXTERN int curl_mvprintf(const char *format, va_list args); +CURL_EXTERN int curl_mvfprintf(FILE *fd, const char *format, va_list args); +CURL_EXTERN int curl_mvsprintf(char *buffer, const char *format, va_list args); +CURL_EXTERN int curl_mvsnprintf(char *buffer, size_t maxlength, + const char *format, va_list args); +CURL_EXTERN char *curl_maprintf(const char *format, ...); +CURL_EXTERN char *curl_mvaprintf(const char *format, va_list args); + +#ifdef _MPRINTF_REPLACE +# undef printf +# undef fprintf +# undef sprintf +# undef vsprintf +# undef snprintf +# undef vprintf +# undef vfprintf +# undef vsnprintf +# undef aprintf +# undef vaprintf +# define printf curl_mprintf +# define fprintf curl_mfprintf +#ifdef CURLDEBUG +/* When built with CURLDEBUG we define away the sprintf functions since we + don't want internal code to be using them */ +# define sprintf sprintf_was_used +# define vsprintf vsprintf_was_used +#else +# define sprintf curl_msprintf +# define vsprintf curl_mvsprintf +#endif +# define snprintf curl_msnprintf +# define vprintf curl_mvprintf +# define vfprintf curl_mvfprintf +# define vsnprintf curl_mvsnprintf +# define aprintf curl_maprintf +# define vaprintf curl_mvaprintf +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __CURL_MPRINTF_H */ diff --git a/deps/src/curl/multi.h b/deps/src/curl/multi.h new file mode 100644 index 000000000..3c4acb0f6 --- /dev/null +++ b/deps/src/curl/multi.h @@ -0,0 +1,399 @@ +#ifndef __CURL_MULTI_H +#define __CURL_MULTI_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ +/* + This is an "external" header file. Don't give away any internals here! + + GOALS + + o Enable a "pull" interface. The application that uses libcurl decides where + and when to ask libcurl to get/send data. + + o Enable multiple simultaneous transfers in the same thread without making it + complicated for the application. + + o Enable the application to select() on its own file descriptors and curl's + file descriptors simultaneous easily. + +*/ + +/* + * This header file should not really need to include "curl.h" since curl.h + * itself includes this file and we expect user applications to do #include + * without the need for especially including multi.h. + * + * For some reason we added this include here at one point, and rather than to + * break existing (wrongly written) libcurl applications, we leave it as-is + * but with this warning attached. + */ +#include "curl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void CURLM; + +typedef enum { + CURLM_CALL_MULTI_PERFORM = -1, /* please call curl_multi_perform() or + curl_multi_socket*() soon */ + CURLM_OK, + CURLM_BAD_HANDLE, /* the passed-in handle is not a valid CURLM handle */ + CURLM_BAD_EASY_HANDLE, /* an easy handle was not good/valid */ + CURLM_OUT_OF_MEMORY, /* if you ever get this, you're in deep sh*t */ + CURLM_INTERNAL_ERROR, /* this is a libcurl bug */ + CURLM_BAD_SOCKET, /* the passed in socket argument did not match */ + CURLM_UNKNOWN_OPTION, /* curl_multi_setopt() with unsupported option */ + CURLM_ADDED_ALREADY, /* an easy handle already added to a multi handle was + attempted to get added - again */ + CURLM_LAST +} CURLMcode; + +/* just to make code nicer when using curl_multi_socket() you can now check + for CURLM_CALL_MULTI_SOCKET too in the same style it works for + curl_multi_perform() and CURLM_CALL_MULTI_PERFORM */ +#define CURLM_CALL_MULTI_SOCKET CURLM_CALL_MULTI_PERFORM + +typedef enum { + CURLMSG_NONE, /* first, not used */ + CURLMSG_DONE, /* This easy handle has completed. 'result' contains + the CURLcode of the transfer */ + CURLMSG_LAST /* last, not used */ +} CURLMSG; + +struct CURLMsg { + CURLMSG msg; /* what this message means */ + CURL *easy_handle; /* the handle it concerns */ + union { + void *whatever; /* message-specific data */ + CURLcode result; /* return code for transfer */ + } data; +}; +typedef struct CURLMsg CURLMsg; + +/* Based on poll(2) structure and values. + * We don't use pollfd and POLL* constants explicitly + * to cover platforms without poll(). */ +#define CURL_WAIT_POLLIN 0x0001 +#define CURL_WAIT_POLLPRI 0x0002 +#define CURL_WAIT_POLLOUT 0x0004 + +struct curl_waitfd { + curl_socket_t fd; + short events; + short revents; /* not supported yet */ +}; + +/* + * Name: curl_multi_init() + * + * Desc: inititalize multi-style curl usage + * + * Returns: a new CURLM handle to use in all 'curl_multi' functions. + */ +CURL_EXTERN CURLM *curl_multi_init(void); + +/* + * Name: curl_multi_add_handle() + * + * Desc: add a standard curl handle to the multi stack + * + * Returns: CURLMcode type, general multi error code. + */ +CURL_EXTERN CURLMcode curl_multi_add_handle(CURLM *multi_handle, + CURL *curl_handle); + + /* + * Name: curl_multi_remove_handle() + * + * Desc: removes a curl handle from the multi stack again + * + * Returns: CURLMcode type, general multi error code. + */ +CURL_EXTERN CURLMcode curl_multi_remove_handle(CURLM *multi_handle, + CURL *curl_handle); + + /* + * Name: curl_multi_fdset() + * + * Desc: Ask curl for its fd_set sets. The app can use these to select() or + * poll() on. We want curl_multi_perform() called as soon as one of + * them are ready. + * + * Returns: CURLMcode type, general multi error code. + */ +CURL_EXTERN CURLMcode curl_multi_fdset(CURLM *multi_handle, + fd_set *read_fd_set, + fd_set *write_fd_set, + fd_set *exc_fd_set, + int *max_fd); + +/* + * Name: curl_multi_wait() + * + * Desc: Poll on all fds within a CURLM set as well as any + * additional fds passed to the function. + * + * Returns: CURLMcode type, general multi error code. + */ +CURL_EXTERN CURLMcode curl_multi_wait(CURLM *multi_handle, + struct curl_waitfd extra_fds[], + unsigned int extra_nfds, + int timeout_ms, + int *ret); + + /* + * Name: curl_multi_perform() + * + * Desc: When the app thinks there's data available for curl it calls this + * function to read/write whatever there is right now. This returns + * as soon as the reads and writes are done. This function does not + * require that there actually is data available for reading or that + * data can be written, it can be called just in case. It returns + * the number of handles that still transfer data in the second + * argument's integer-pointer. + * + * Returns: CURLMcode type, general multi error code. *NOTE* that this only + * returns errors etc regarding the whole multi stack. There might + * still have occurred problems on invidual transfers even when this + * returns OK. + */ +CURL_EXTERN CURLMcode curl_multi_perform(CURLM *multi_handle, + int *running_handles); + + /* + * Name: curl_multi_cleanup() + * + * Desc: Cleans up and removes a whole multi stack. It does not free or + * touch any individual easy handles in any way. We need to define + * in what state those handles will be if this function is called + * in the middle of a transfer. + * + * Returns: CURLMcode type, general multi error code. + */ +CURL_EXTERN CURLMcode curl_multi_cleanup(CURLM *multi_handle); + +/* + * Name: curl_multi_info_read() + * + * Desc: Ask the multi handle if there's any messages/informationals from + * the individual transfers. Messages include informationals such as + * error code from the transfer or just the fact that a transfer is + * completed. More details on these should be written down as well. + * + * Repeated calls to this function will return a new struct each + * time, until a special "end of msgs" struct is returned as a signal + * that there is no more to get at this point. + * + * The data the returned pointer points to will not survive calling + * curl_multi_cleanup(). + * + * The 'CURLMsg' struct is meant to be very simple and only contain + * very basic informations. If more involved information is wanted, + * we will provide the particular "transfer handle" in that struct + * and that should/could/would be used in subsequent + * curl_easy_getinfo() calls (or similar). The point being that we + * must never expose complex structs to applications, as then we'll + * undoubtably get backwards compatibility problems in the future. + * + * Returns: A pointer to a filled-in struct, or NULL if it failed or ran out + * of structs. It also writes the number of messages left in the + * queue (after this read) in the integer the second argument points + * to. + */ +CURL_EXTERN CURLMsg *curl_multi_info_read(CURLM *multi_handle, + int *msgs_in_queue); + +/* + * Name: curl_multi_strerror() + * + * Desc: The curl_multi_strerror function may be used to turn a CURLMcode + * value into the equivalent human readable error string. This is + * useful for printing meaningful error messages. + * + * Returns: A pointer to a zero-terminated error message. + */ +CURL_EXTERN const char *curl_multi_strerror(CURLMcode); + +/* + * Name: curl_multi_socket() and + * curl_multi_socket_all() + * + * Desc: An alternative version of curl_multi_perform() that allows the + * application to pass in one of the file descriptors that have been + * detected to have "action" on them and let libcurl perform. + * See man page for details. + */ +#define CURL_POLL_NONE 0 +#define CURL_POLL_IN 1 +#define CURL_POLL_OUT 2 +#define CURL_POLL_INOUT 3 +#define CURL_POLL_REMOVE 4 + +#define CURL_SOCKET_TIMEOUT CURL_SOCKET_BAD + +#define CURL_CSELECT_IN 0x01 +#define CURL_CSELECT_OUT 0x02 +#define CURL_CSELECT_ERR 0x04 + +typedef int (*curl_socket_callback)(CURL *easy, /* easy handle */ + curl_socket_t s, /* socket */ + int what, /* see above */ + void *userp, /* private callback + pointer */ + void *socketp); /* private socket + pointer */ +/* + * Name: curl_multi_timer_callback + * + * Desc: Called by libcurl whenever the library detects a change in the + * maximum number of milliseconds the app is allowed to wait before + * curl_multi_socket() or curl_multi_perform() must be called + * (to allow libcurl's timed events to take place). + * + * Returns: The callback should return zero. + */ +typedef int (*curl_multi_timer_callback)(CURLM *multi, /* multi handle */ + long timeout_ms, /* see above */ + void *userp); /* private callback + pointer */ + +CURL_EXTERN CURLMcode curl_multi_socket(CURLM *multi_handle, curl_socket_t s, + int *running_handles); + +CURL_EXTERN CURLMcode curl_multi_socket_action(CURLM *multi_handle, + curl_socket_t s, + int ev_bitmask, + int *running_handles); + +CURL_EXTERN CURLMcode curl_multi_socket_all(CURLM *multi_handle, + int *running_handles); + +#ifndef CURL_ALLOW_OLD_MULTI_SOCKET +/* This macro below was added in 7.16.3 to push users who recompile to use + the new curl_multi_socket_action() instead of the old curl_multi_socket() +*/ +#define curl_multi_socket(x,y,z) curl_multi_socket_action(x,y,0,z) +#endif + +/* + * Name: curl_multi_timeout() + * + * Desc: Returns the maximum number of milliseconds the app is allowed to + * wait before curl_multi_socket() or curl_multi_perform() must be + * called (to allow libcurl's timed events to take place). + * + * Returns: CURLM error code. + */ +CURL_EXTERN CURLMcode curl_multi_timeout(CURLM *multi_handle, + long *milliseconds); + +#undef CINIT /* re-using the same name as in curl.h */ + +#ifdef CURL_ISOCPP +#define CINIT(name,type,num) CURLMOPT_ ## name = CURLOPTTYPE_ ## type + num +#else +/* The macro "##" is ISO C, we assume pre-ISO C doesn't support it. */ +#define LONG CURLOPTTYPE_LONG +#define OBJECTPOINT CURLOPTTYPE_OBJECTPOINT +#define FUNCTIONPOINT CURLOPTTYPE_FUNCTIONPOINT +#define OFF_T CURLOPTTYPE_OFF_T +#define CINIT(name,type,number) CURLMOPT_/**/name = type + number +#endif + +typedef enum { + /* This is the socket callback function pointer */ + CINIT(SOCKETFUNCTION, FUNCTIONPOINT, 1), + + /* This is the argument passed to the socket callback */ + CINIT(SOCKETDATA, OBJECTPOINT, 2), + + /* set to 1 to enable pipelining for this multi handle */ + CINIT(PIPELINING, LONG, 3), + + /* This is the timer callback function pointer */ + CINIT(TIMERFUNCTION, FUNCTIONPOINT, 4), + + /* This is the argument passed to the timer callback */ + CINIT(TIMERDATA, OBJECTPOINT, 5), + + /* maximum number of entries in the connection cache */ + CINIT(MAXCONNECTS, LONG, 6), + + /* maximum number of (pipelining) connections to one host */ + CINIT(MAX_HOST_CONNECTIONS, LONG, 7), + + /* maximum number of requests in a pipeline */ + CINIT(MAX_PIPELINE_LENGTH, LONG, 8), + + /* a connection with a content-length longer than this + will not be considered for pipelining */ + CINIT(CONTENT_LENGTH_PENALTY_SIZE, OFF_T, 9), + + /* a connection with a chunk length longer than this + will not be considered for pipelining */ + CINIT(CHUNK_LENGTH_PENALTY_SIZE, OFF_T, 10), + + /* a list of site names(+port) that are blacklisted from + pipelining */ + CINIT(PIPELINING_SITE_BL, OBJECTPOINT, 11), + + /* a list of server types that are blacklisted from + pipelining */ + CINIT(PIPELINING_SERVER_BL, OBJECTPOINT, 12), + + /* maximum number of open connections in total */ + CINIT(MAX_TOTAL_CONNECTIONS, LONG, 13), + + CURLMOPT_LASTENTRY /* the last unused */ +} CURLMoption; + + +/* + * Name: curl_multi_setopt() + * + * Desc: Sets options for the multi handle. + * + * Returns: CURLM error code. + */ +CURL_EXTERN CURLMcode curl_multi_setopt(CURLM *multi_handle, + CURLMoption option, ...); + + +/* + * Name: curl_multi_assign() + * + * Desc: This function sets an association in the multi handle between the + * given socket and a private pointer of the application. This is + * (only) useful for curl_multi_socket uses. + * + * Returns: CURLM error code. + */ +CURL_EXTERN CURLMcode curl_multi_assign(CURLM *multi_handle, + curl_socket_t sockfd, void *sockp); + +#ifdef __cplusplus +} /* end of extern "C" */ +#endif + +#endif diff --git a/deps/src/curl/stdcheaders.h b/deps/src/curl/stdcheaders.h new file mode 100644 index 000000000..ad82ef633 --- /dev/null +++ b/deps/src/curl/stdcheaders.h @@ -0,0 +1,33 @@ +#ifndef __STDC_HEADERS_H +#define __STDC_HEADERS_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2010, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include + +size_t fread (void *, size_t, size_t, FILE *); +size_t fwrite (const void *, size_t, size_t, FILE *); + +int strcasecmp(const char *, const char *); +int strncasecmp(const char *, const char *, size_t); + +#endif /* __STDC_HEADERS_H */ diff --git a/deps/src/curl/typecheck-gcc.h b/deps/src/curl/typecheck-gcc.h new file mode 100644 index 000000000..cdeba21a2 --- /dev/null +++ b/deps/src/curl/typecheck-gcc.h @@ -0,0 +1,610 @@ +#ifndef __CURL_TYPECHECK_GCC_H +#define __CURL_TYPECHECK_GCC_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/* wraps curl_easy_setopt() with typechecking */ + +/* To add a new kind of warning, add an + * if(_curl_is_sometype_option(_curl_opt)) + * if(!_curl_is_sometype(value)) + * _curl_easy_setopt_err_sometype(); + * block and define _curl_is_sometype_option, _curl_is_sometype and + * _curl_easy_setopt_err_sometype below + * + * NOTE: We use two nested 'if' statements here instead of the && operator, in + * order to work around gcc bug #32061. It affects only gcc 4.3.x/4.4.x + * when compiling with -Wlogical-op. + * + * To add an option that uses the same type as an existing option, you'll just + * need to extend the appropriate _curl_*_option macro + */ +#define curl_easy_setopt(handle, option, value) \ +__extension__ ({ \ + __typeof__ (option) _curl_opt = option; \ + if(__builtin_constant_p(_curl_opt)) { \ + if(_curl_is_long_option(_curl_opt)) \ + if(!_curl_is_long(value)) \ + _curl_easy_setopt_err_long(); \ + if(_curl_is_off_t_option(_curl_opt)) \ + if(!_curl_is_off_t(value)) \ + _curl_easy_setopt_err_curl_off_t(); \ + if(_curl_is_string_option(_curl_opt)) \ + if(!_curl_is_string(value)) \ + _curl_easy_setopt_err_string(); \ + if(_curl_is_write_cb_option(_curl_opt)) \ + if(!_curl_is_write_cb(value)) \ + _curl_easy_setopt_err_write_callback(); \ + if((_curl_opt) == CURLOPT_READFUNCTION) \ + if(!_curl_is_read_cb(value)) \ + _curl_easy_setopt_err_read_cb(); \ + if((_curl_opt) == CURLOPT_IOCTLFUNCTION) \ + if(!_curl_is_ioctl_cb(value)) \ + _curl_easy_setopt_err_ioctl_cb(); \ + if((_curl_opt) == CURLOPT_SOCKOPTFUNCTION) \ + if(!_curl_is_sockopt_cb(value)) \ + _curl_easy_setopt_err_sockopt_cb(); \ + if((_curl_opt) == CURLOPT_OPENSOCKETFUNCTION) \ + if(!_curl_is_opensocket_cb(value)) \ + _curl_easy_setopt_err_opensocket_cb(); \ + if((_curl_opt) == CURLOPT_PROGRESSFUNCTION) \ + if(!_curl_is_progress_cb(value)) \ + _curl_easy_setopt_err_progress_cb(); \ + if((_curl_opt) == CURLOPT_DEBUGFUNCTION) \ + if(!_curl_is_debug_cb(value)) \ + _curl_easy_setopt_err_debug_cb(); \ + if((_curl_opt) == CURLOPT_SSL_CTX_FUNCTION) \ + if(!_curl_is_ssl_ctx_cb(value)) \ + _curl_easy_setopt_err_ssl_ctx_cb(); \ + if(_curl_is_conv_cb_option(_curl_opt)) \ + if(!_curl_is_conv_cb(value)) \ + _curl_easy_setopt_err_conv_cb(); \ + if((_curl_opt) == CURLOPT_SEEKFUNCTION) \ + if(!_curl_is_seek_cb(value)) \ + _curl_easy_setopt_err_seek_cb(); \ + if(_curl_is_cb_data_option(_curl_opt)) \ + if(!_curl_is_cb_data(value)) \ + _curl_easy_setopt_err_cb_data(); \ + if((_curl_opt) == CURLOPT_ERRORBUFFER) \ + if(!_curl_is_error_buffer(value)) \ + _curl_easy_setopt_err_error_buffer(); \ + if((_curl_opt) == CURLOPT_STDERR) \ + if(!_curl_is_FILE(value)) \ + _curl_easy_setopt_err_FILE(); \ + if(_curl_is_postfields_option(_curl_opt)) \ + if(!_curl_is_postfields(value)) \ + _curl_easy_setopt_err_postfields(); \ + if((_curl_opt) == CURLOPT_HTTPPOST) \ + if(!_curl_is_arr((value), struct curl_httppost)) \ + _curl_easy_setopt_err_curl_httpost(); \ + if(_curl_is_slist_option(_curl_opt)) \ + if(!_curl_is_arr((value), struct curl_slist)) \ + _curl_easy_setopt_err_curl_slist(); \ + if((_curl_opt) == CURLOPT_SHARE) \ + if(!_curl_is_ptr((value), CURLSH)) \ + _curl_easy_setopt_err_CURLSH(); \ + } \ + curl_easy_setopt(handle, _curl_opt, value); \ +}) + +/* wraps curl_easy_getinfo() with typechecking */ +/* FIXME: don't allow const pointers */ +#define curl_easy_getinfo(handle, info, arg) \ +__extension__ ({ \ + __typeof__ (info) _curl_info = info; \ + if(__builtin_constant_p(_curl_info)) { \ + if(_curl_is_string_info(_curl_info)) \ + if(!_curl_is_arr((arg), char *)) \ + _curl_easy_getinfo_err_string(); \ + if(_curl_is_long_info(_curl_info)) \ + if(!_curl_is_arr((arg), long)) \ + _curl_easy_getinfo_err_long(); \ + if(_curl_is_double_info(_curl_info)) \ + if(!_curl_is_arr((arg), double)) \ + _curl_easy_getinfo_err_double(); \ + if(_curl_is_slist_info(_curl_info)) \ + if(!_curl_is_arr((arg), struct curl_slist *)) \ + _curl_easy_getinfo_err_curl_slist(); \ + } \ + curl_easy_getinfo(handle, _curl_info, arg); \ +}) + +/* TODO: typechecking for curl_share_setopt() and curl_multi_setopt(), + * for now just make sure that the functions are called with three + * arguments + */ +#define curl_share_setopt(share,opt,param) curl_share_setopt(share,opt,param) +#define curl_multi_setopt(handle,opt,param) curl_multi_setopt(handle,opt,param) + + +/* the actual warnings, triggered by calling the _curl_easy_setopt_err* + * functions */ + +/* To define a new warning, use _CURL_WARNING(identifier, "message") */ +#define _CURL_WARNING(id, message) \ + static void __attribute__((__warning__(message))) \ + __attribute__((__unused__)) __attribute__((__noinline__)) \ + id(void) { __asm__(""); } + +_CURL_WARNING(_curl_easy_setopt_err_long, + "curl_easy_setopt expects a long argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_curl_off_t, + "curl_easy_setopt expects a curl_off_t argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_string, + "curl_easy_setopt expects a " + "string (char* or char[]) argument for this option" + ) +_CURL_WARNING(_curl_easy_setopt_err_write_callback, + "curl_easy_setopt expects a curl_write_callback argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_read_cb, + "curl_easy_setopt expects a curl_read_callback argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_ioctl_cb, + "curl_easy_setopt expects a curl_ioctl_callback argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_sockopt_cb, + "curl_easy_setopt expects a curl_sockopt_callback argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_opensocket_cb, + "curl_easy_setopt expects a " + "curl_opensocket_callback argument for this option" + ) +_CURL_WARNING(_curl_easy_setopt_err_progress_cb, + "curl_easy_setopt expects a curl_progress_callback argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_debug_cb, + "curl_easy_setopt expects a curl_debug_callback argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_ssl_ctx_cb, + "curl_easy_setopt expects a curl_ssl_ctx_callback argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_conv_cb, + "curl_easy_setopt expects a curl_conv_callback argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_seek_cb, + "curl_easy_setopt expects a curl_seek_callback argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_cb_data, + "curl_easy_setopt expects a " + "private data pointer as argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_error_buffer, + "curl_easy_setopt expects a " + "char buffer of CURL_ERROR_SIZE as argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_FILE, + "curl_easy_setopt expects a FILE* argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_postfields, + "curl_easy_setopt expects a void* or char* argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_curl_httpost, + "curl_easy_setopt expects a struct curl_httppost* argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_curl_slist, + "curl_easy_setopt expects a struct curl_slist* argument for this option") +_CURL_WARNING(_curl_easy_setopt_err_CURLSH, + "curl_easy_setopt expects a CURLSH* argument for this option") + +_CURL_WARNING(_curl_easy_getinfo_err_string, + "curl_easy_getinfo expects a pointer to char * for this info") +_CURL_WARNING(_curl_easy_getinfo_err_long, + "curl_easy_getinfo expects a pointer to long for this info") +_CURL_WARNING(_curl_easy_getinfo_err_double, + "curl_easy_getinfo expects a pointer to double for this info") +_CURL_WARNING(_curl_easy_getinfo_err_curl_slist, + "curl_easy_getinfo expects a pointer to struct curl_slist * for this info") + +/* groups of curl_easy_setops options that take the same type of argument */ + +/* To add a new option to one of the groups, just add + * (option) == CURLOPT_SOMETHING + * to the or-expression. If the option takes a long or curl_off_t, you don't + * have to do anything + */ + +/* evaluates to true if option takes a long argument */ +#define _curl_is_long_option(option) \ + (0 < (option) && (option) < CURLOPTTYPE_OBJECTPOINT) + +#define _curl_is_off_t_option(option) \ + ((option) > CURLOPTTYPE_OFF_T) + +/* evaluates to true if option takes a char* argument */ +#define _curl_is_string_option(option) \ + ((option) == CURLOPT_URL || \ + (option) == CURLOPT_PROXY || \ + (option) == CURLOPT_INTERFACE || \ + (option) == CURLOPT_NETRC_FILE || \ + (option) == CURLOPT_USERPWD || \ + (option) == CURLOPT_USERNAME || \ + (option) == CURLOPT_PASSWORD || \ + (option) == CURLOPT_PROXYUSERPWD || \ + (option) == CURLOPT_PROXYUSERNAME || \ + (option) == CURLOPT_PROXYPASSWORD || \ + (option) == CURLOPT_NOPROXY || \ + (option) == CURLOPT_ACCEPT_ENCODING || \ + (option) == CURLOPT_REFERER || \ + (option) == CURLOPT_USERAGENT || \ + (option) == CURLOPT_COOKIE || \ + (option) == CURLOPT_COOKIEFILE || \ + (option) == CURLOPT_COOKIEJAR || \ + (option) == CURLOPT_COOKIELIST || \ + (option) == CURLOPT_FTPPORT || \ + (option) == CURLOPT_FTP_ALTERNATIVE_TO_USER || \ + (option) == CURLOPT_FTP_ACCOUNT || \ + (option) == CURLOPT_RANGE || \ + (option) == CURLOPT_CUSTOMREQUEST || \ + (option) == CURLOPT_SSLCERT || \ + (option) == CURLOPT_SSLCERTTYPE || \ + (option) == CURLOPT_SSLKEY || \ + (option) == CURLOPT_SSLKEYTYPE || \ + (option) == CURLOPT_KEYPASSWD || \ + (option) == CURLOPT_SSLENGINE || \ + (option) == CURLOPT_CAINFO || \ + (option) == CURLOPT_CAPATH || \ + (option) == CURLOPT_RANDOM_FILE || \ + (option) == CURLOPT_EGDSOCKET || \ + (option) == CURLOPT_SSL_CIPHER_LIST || \ + (option) == CURLOPT_KRBLEVEL || \ + (option) == CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 || \ + (option) == CURLOPT_SSH_PUBLIC_KEYFILE || \ + (option) == CURLOPT_SSH_PRIVATE_KEYFILE || \ + (option) == CURLOPT_CRLFILE || \ + (option) == CURLOPT_ISSUERCERT || \ + (option) == CURLOPT_SOCKS5_GSSAPI_SERVICE || \ + (option) == CURLOPT_SSH_KNOWNHOSTS || \ + (option) == CURLOPT_MAIL_FROM || \ + (option) == CURLOPT_RTSP_SESSION_ID || \ + (option) == CURLOPT_RTSP_STREAM_URI || \ + (option) == CURLOPT_RTSP_TRANSPORT || \ + (option) == CURLOPT_XOAUTH2_BEARER || \ + (option) == CURLOPT_DNS_SERVERS || \ + (option) == CURLOPT_DNS_INTERFACE || \ + (option) == CURLOPT_DNS_LOCAL_IP4 || \ + (option) == CURLOPT_DNS_LOCAL_IP6 || \ + (option) == CURLOPT_LOGIN_OPTIONS || \ + 0) + +/* evaluates to true if option takes a curl_write_callback argument */ +#define _curl_is_write_cb_option(option) \ + ((option) == CURLOPT_HEADERFUNCTION || \ + (option) == CURLOPT_WRITEFUNCTION) + +/* evaluates to true if option takes a curl_conv_callback argument */ +#define _curl_is_conv_cb_option(option) \ + ((option) == CURLOPT_CONV_TO_NETWORK_FUNCTION || \ + (option) == CURLOPT_CONV_FROM_NETWORK_FUNCTION || \ + (option) == CURLOPT_CONV_FROM_UTF8_FUNCTION) + +/* evaluates to true if option takes a data argument to pass to a callback */ +#define _curl_is_cb_data_option(option) \ + ((option) == CURLOPT_WRITEDATA || \ + (option) == CURLOPT_READDATA || \ + (option) == CURLOPT_IOCTLDATA || \ + (option) == CURLOPT_SOCKOPTDATA || \ + (option) == CURLOPT_OPENSOCKETDATA || \ + (option) == CURLOPT_PROGRESSDATA || \ + (option) == CURLOPT_WRITEHEADER || \ + (option) == CURLOPT_DEBUGDATA || \ + (option) == CURLOPT_SSL_CTX_DATA || \ + (option) == CURLOPT_SEEKDATA || \ + (option) == CURLOPT_PRIVATE || \ + (option) == CURLOPT_SSH_KEYDATA || \ + (option) == CURLOPT_INTERLEAVEDATA || \ + (option) == CURLOPT_CHUNK_DATA || \ + (option) == CURLOPT_FNMATCH_DATA || \ + 0) + +/* evaluates to true if option takes a POST data argument (void* or char*) */ +#define _curl_is_postfields_option(option) \ + ((option) == CURLOPT_POSTFIELDS || \ + (option) == CURLOPT_COPYPOSTFIELDS || \ + 0) + +/* evaluates to true if option takes a struct curl_slist * argument */ +#define _curl_is_slist_option(option) \ + ((option) == CURLOPT_HTTPHEADER || \ + (option) == CURLOPT_HTTP200ALIASES || \ + (option) == CURLOPT_QUOTE || \ + (option) == CURLOPT_POSTQUOTE || \ + (option) == CURLOPT_PREQUOTE || \ + (option) == CURLOPT_TELNETOPTIONS || \ + (option) == CURLOPT_MAIL_RCPT || \ + 0) + +/* groups of curl_easy_getinfo infos that take the same type of argument */ + +/* evaluates to true if info expects a pointer to char * argument */ +#define _curl_is_string_info(info) \ + (CURLINFO_STRING < (info) && (info) < CURLINFO_LONG) + +/* evaluates to true if info expects a pointer to long argument */ +#define _curl_is_long_info(info) \ + (CURLINFO_LONG < (info) && (info) < CURLINFO_DOUBLE) + +/* evaluates to true if info expects a pointer to double argument */ +#define _curl_is_double_info(info) \ + (CURLINFO_DOUBLE < (info) && (info) < CURLINFO_SLIST) + +/* true if info expects a pointer to struct curl_slist * argument */ +#define _curl_is_slist_info(info) \ + (CURLINFO_SLIST < (info)) + + +/* typecheck helpers -- check whether given expression has requested type*/ + +/* For pointers, you can use the _curl_is_ptr/_curl_is_arr macros, + * otherwise define a new macro. Search for __builtin_types_compatible_p + * in the GCC manual. + * NOTE: these macros MUST NOT EVALUATE their arguments! The argument is + * the actual expression passed to the curl_easy_setopt macro. This + * means that you can only apply the sizeof and __typeof__ operators, no + * == or whatsoever. + */ + +/* XXX: should evaluate to true iff expr is a pointer */ +#define _curl_is_any_ptr(expr) \ + (sizeof(expr) == sizeof(void*)) + +/* evaluates to true if expr is NULL */ +/* XXX: must not evaluate expr, so this check is not accurate */ +#define _curl_is_NULL(expr) \ + (__builtin_types_compatible_p(__typeof__(expr), __typeof__(NULL))) + +/* evaluates to true if expr is type*, const type* or NULL */ +#define _curl_is_ptr(expr, type) \ + (_curl_is_NULL(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), type *) || \ + __builtin_types_compatible_p(__typeof__(expr), const type *)) + +/* evaluates to true if expr is one of type[], type*, NULL or const type* */ +#define _curl_is_arr(expr, type) \ + (_curl_is_ptr((expr), type) || \ + __builtin_types_compatible_p(__typeof__(expr), type [])) + +/* evaluates to true if expr is a string */ +#define _curl_is_string(expr) \ + (_curl_is_arr((expr), char) || \ + _curl_is_arr((expr), signed char) || \ + _curl_is_arr((expr), unsigned char)) + +/* evaluates to true if expr is a long (no matter the signedness) + * XXX: for now, int is also accepted (and therefore short and char, which + * are promoted to int when passed to a variadic function) */ +#define _curl_is_long(expr) \ + (__builtin_types_compatible_p(__typeof__(expr), long) || \ + __builtin_types_compatible_p(__typeof__(expr), signed long) || \ + __builtin_types_compatible_p(__typeof__(expr), unsigned long) || \ + __builtin_types_compatible_p(__typeof__(expr), int) || \ + __builtin_types_compatible_p(__typeof__(expr), signed int) || \ + __builtin_types_compatible_p(__typeof__(expr), unsigned int) || \ + __builtin_types_compatible_p(__typeof__(expr), short) || \ + __builtin_types_compatible_p(__typeof__(expr), signed short) || \ + __builtin_types_compatible_p(__typeof__(expr), unsigned short) || \ + __builtin_types_compatible_p(__typeof__(expr), char) || \ + __builtin_types_compatible_p(__typeof__(expr), signed char) || \ + __builtin_types_compatible_p(__typeof__(expr), unsigned char)) + +/* evaluates to true if expr is of type curl_off_t */ +#define _curl_is_off_t(expr) \ + (__builtin_types_compatible_p(__typeof__(expr), curl_off_t)) + +/* evaluates to true if expr is abuffer suitable for CURLOPT_ERRORBUFFER */ +/* XXX: also check size of an char[] array? */ +#define _curl_is_error_buffer(expr) \ + (_curl_is_NULL(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), char *) || \ + __builtin_types_compatible_p(__typeof__(expr), char[])) + +/* evaluates to true if expr is of type (const) void* or (const) FILE* */ +#if 0 +#define _curl_is_cb_data(expr) \ + (_curl_is_ptr((expr), void) || \ + _curl_is_ptr((expr), FILE)) +#else /* be less strict */ +#define _curl_is_cb_data(expr) \ + _curl_is_any_ptr(expr) +#endif + +/* evaluates to true if expr is of type FILE* */ +#define _curl_is_FILE(expr) \ + (__builtin_types_compatible_p(__typeof__(expr), FILE *)) + +/* evaluates to true if expr can be passed as POST data (void* or char*) */ +#define _curl_is_postfields(expr) \ + (_curl_is_ptr((expr), void) || \ + _curl_is_arr((expr), char)) + +/* FIXME: the whole callback checking is messy... + * The idea is to tolerate char vs. void and const vs. not const + * pointers in arguments at least + */ +/* helper: __builtin_types_compatible_p distinguishes between functions and + * function pointers, hide it */ +#define _curl_callback_compatible(func, type) \ + (__builtin_types_compatible_p(__typeof__(func), type) || \ + __builtin_types_compatible_p(__typeof__(func), type*)) + +/* evaluates to true if expr is of type curl_read_callback or "similar" */ +#define _curl_is_read_cb(expr) \ + (_curl_is_NULL(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), __typeof__(fread)) || \ + __builtin_types_compatible_p(__typeof__(expr), curl_read_callback) || \ + _curl_callback_compatible((expr), _curl_read_callback1) || \ + _curl_callback_compatible((expr), _curl_read_callback2) || \ + _curl_callback_compatible((expr), _curl_read_callback3) || \ + _curl_callback_compatible((expr), _curl_read_callback4) || \ + _curl_callback_compatible((expr), _curl_read_callback5) || \ + _curl_callback_compatible((expr), _curl_read_callback6)) +typedef size_t (_curl_read_callback1)(char *, size_t, size_t, void*); +typedef size_t (_curl_read_callback2)(char *, size_t, size_t, const void*); +typedef size_t (_curl_read_callback3)(char *, size_t, size_t, FILE*); +typedef size_t (_curl_read_callback4)(void *, size_t, size_t, void*); +typedef size_t (_curl_read_callback5)(void *, size_t, size_t, const void*); +typedef size_t (_curl_read_callback6)(void *, size_t, size_t, FILE*); + +/* evaluates to true if expr is of type curl_write_callback or "similar" */ +#define _curl_is_write_cb(expr) \ + (_curl_is_read_cb(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), __typeof__(fwrite)) || \ + __builtin_types_compatible_p(__typeof__(expr), curl_write_callback) || \ + _curl_callback_compatible((expr), _curl_write_callback1) || \ + _curl_callback_compatible((expr), _curl_write_callback2) || \ + _curl_callback_compatible((expr), _curl_write_callback3) || \ + _curl_callback_compatible((expr), _curl_write_callback4) || \ + _curl_callback_compatible((expr), _curl_write_callback5) || \ + _curl_callback_compatible((expr), _curl_write_callback6)) +typedef size_t (_curl_write_callback1)(const char *, size_t, size_t, void*); +typedef size_t (_curl_write_callback2)(const char *, size_t, size_t, + const void*); +typedef size_t (_curl_write_callback3)(const char *, size_t, size_t, FILE*); +typedef size_t (_curl_write_callback4)(const void *, size_t, size_t, void*); +typedef size_t (_curl_write_callback5)(const void *, size_t, size_t, + const void*); +typedef size_t (_curl_write_callback6)(const void *, size_t, size_t, FILE*); + +/* evaluates to true if expr is of type curl_ioctl_callback or "similar" */ +#define _curl_is_ioctl_cb(expr) \ + (_curl_is_NULL(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), curl_ioctl_callback) || \ + _curl_callback_compatible((expr), _curl_ioctl_callback1) || \ + _curl_callback_compatible((expr), _curl_ioctl_callback2) || \ + _curl_callback_compatible((expr), _curl_ioctl_callback3) || \ + _curl_callback_compatible((expr), _curl_ioctl_callback4)) +typedef curlioerr (_curl_ioctl_callback1)(CURL *, int, void*); +typedef curlioerr (_curl_ioctl_callback2)(CURL *, int, const void*); +typedef curlioerr (_curl_ioctl_callback3)(CURL *, curliocmd, void*); +typedef curlioerr (_curl_ioctl_callback4)(CURL *, curliocmd, const void*); + +/* evaluates to true if expr is of type curl_sockopt_callback or "similar" */ +#define _curl_is_sockopt_cb(expr) \ + (_curl_is_NULL(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), curl_sockopt_callback) || \ + _curl_callback_compatible((expr), _curl_sockopt_callback1) || \ + _curl_callback_compatible((expr), _curl_sockopt_callback2)) +typedef int (_curl_sockopt_callback1)(void *, curl_socket_t, curlsocktype); +typedef int (_curl_sockopt_callback2)(const void *, curl_socket_t, + curlsocktype); + +/* evaluates to true if expr is of type curl_opensocket_callback or + "similar" */ +#define _curl_is_opensocket_cb(expr) \ + (_curl_is_NULL(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), curl_opensocket_callback) ||\ + _curl_callback_compatible((expr), _curl_opensocket_callback1) || \ + _curl_callback_compatible((expr), _curl_opensocket_callback2) || \ + _curl_callback_compatible((expr), _curl_opensocket_callback3) || \ + _curl_callback_compatible((expr), _curl_opensocket_callback4)) +typedef curl_socket_t (_curl_opensocket_callback1) + (void *, curlsocktype, struct curl_sockaddr *); +typedef curl_socket_t (_curl_opensocket_callback2) + (void *, curlsocktype, const struct curl_sockaddr *); +typedef curl_socket_t (_curl_opensocket_callback3) + (const void *, curlsocktype, struct curl_sockaddr *); +typedef curl_socket_t (_curl_opensocket_callback4) + (const void *, curlsocktype, const struct curl_sockaddr *); + +/* evaluates to true if expr is of type curl_progress_callback or "similar" */ +#define _curl_is_progress_cb(expr) \ + (_curl_is_NULL(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), curl_progress_callback) || \ + _curl_callback_compatible((expr), _curl_progress_callback1) || \ + _curl_callback_compatible((expr), _curl_progress_callback2)) +typedef int (_curl_progress_callback1)(void *, + double, double, double, double); +typedef int (_curl_progress_callback2)(const void *, + double, double, double, double); + +/* evaluates to true if expr is of type curl_debug_callback or "similar" */ +#define _curl_is_debug_cb(expr) \ + (_curl_is_NULL(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), curl_debug_callback) || \ + _curl_callback_compatible((expr), _curl_debug_callback1) || \ + _curl_callback_compatible((expr), _curl_debug_callback2) || \ + _curl_callback_compatible((expr), _curl_debug_callback3) || \ + _curl_callback_compatible((expr), _curl_debug_callback4) || \ + _curl_callback_compatible((expr), _curl_debug_callback5) || \ + _curl_callback_compatible((expr), _curl_debug_callback6) || \ + _curl_callback_compatible((expr), _curl_debug_callback7) || \ + _curl_callback_compatible((expr), _curl_debug_callback8)) +typedef int (_curl_debug_callback1) (CURL *, + curl_infotype, char *, size_t, void *); +typedef int (_curl_debug_callback2) (CURL *, + curl_infotype, char *, size_t, const void *); +typedef int (_curl_debug_callback3) (CURL *, + curl_infotype, const char *, size_t, void *); +typedef int (_curl_debug_callback4) (CURL *, + curl_infotype, const char *, size_t, const void *); +typedef int (_curl_debug_callback5) (CURL *, + curl_infotype, unsigned char *, size_t, void *); +typedef int (_curl_debug_callback6) (CURL *, + curl_infotype, unsigned char *, size_t, const void *); +typedef int (_curl_debug_callback7) (CURL *, + curl_infotype, const unsigned char *, size_t, void *); +typedef int (_curl_debug_callback8) (CURL *, + curl_infotype, const unsigned char *, size_t, const void *); + +/* evaluates to true if expr is of type curl_ssl_ctx_callback or "similar" */ +/* this is getting even messier... */ +#define _curl_is_ssl_ctx_cb(expr) \ + (_curl_is_NULL(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), curl_ssl_ctx_callback) || \ + _curl_callback_compatible((expr), _curl_ssl_ctx_callback1) || \ + _curl_callback_compatible((expr), _curl_ssl_ctx_callback2) || \ + _curl_callback_compatible((expr), _curl_ssl_ctx_callback3) || \ + _curl_callback_compatible((expr), _curl_ssl_ctx_callback4) || \ + _curl_callback_compatible((expr), _curl_ssl_ctx_callback5) || \ + _curl_callback_compatible((expr), _curl_ssl_ctx_callback6) || \ + _curl_callback_compatible((expr), _curl_ssl_ctx_callback7) || \ + _curl_callback_compatible((expr), _curl_ssl_ctx_callback8)) +typedef CURLcode (_curl_ssl_ctx_callback1)(CURL *, void *, void *); +typedef CURLcode (_curl_ssl_ctx_callback2)(CURL *, void *, const void *); +typedef CURLcode (_curl_ssl_ctx_callback3)(CURL *, const void *, void *); +typedef CURLcode (_curl_ssl_ctx_callback4)(CURL *, const void *, const void *); +#ifdef HEADER_SSL_H +/* hack: if we included OpenSSL's ssl.h, we know about SSL_CTX + * this will of course break if we're included before OpenSSL headers... + */ +typedef CURLcode (_curl_ssl_ctx_callback5)(CURL *, SSL_CTX, void *); +typedef CURLcode (_curl_ssl_ctx_callback6)(CURL *, SSL_CTX, const void *); +typedef CURLcode (_curl_ssl_ctx_callback7)(CURL *, const SSL_CTX, void *); +typedef CURLcode (_curl_ssl_ctx_callback8)(CURL *, const SSL_CTX, + const void *); +#else +typedef _curl_ssl_ctx_callback1 _curl_ssl_ctx_callback5; +typedef _curl_ssl_ctx_callback1 _curl_ssl_ctx_callback6; +typedef _curl_ssl_ctx_callback1 _curl_ssl_ctx_callback7; +typedef _curl_ssl_ctx_callback1 _curl_ssl_ctx_callback8; +#endif + +/* evaluates to true if expr is of type curl_conv_callback or "similar" */ +#define _curl_is_conv_cb(expr) \ + (_curl_is_NULL(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), curl_conv_callback) || \ + _curl_callback_compatible((expr), _curl_conv_callback1) || \ + _curl_callback_compatible((expr), _curl_conv_callback2) || \ + _curl_callback_compatible((expr), _curl_conv_callback3) || \ + _curl_callback_compatible((expr), _curl_conv_callback4)) +typedef CURLcode (*_curl_conv_callback1)(char *, size_t length); +typedef CURLcode (*_curl_conv_callback2)(const char *, size_t length); +typedef CURLcode (*_curl_conv_callback3)(void *, size_t length); +typedef CURLcode (*_curl_conv_callback4)(const void *, size_t length); + +/* evaluates to true if expr is of type curl_seek_callback or "similar" */ +#define _curl_is_seek_cb(expr) \ + (_curl_is_NULL(expr) || \ + __builtin_types_compatible_p(__typeof__(expr), curl_seek_callback) || \ + _curl_callback_compatible((expr), _curl_seek_callback1) || \ + _curl_callback_compatible((expr), _curl_seek_callback2)) +typedef CURLcode (*_curl_seek_callback1)(void *, curl_off_t, int); +typedef CURLcode (*_curl_seek_callback2)(const void *, curl_off_t, int); + + +#endif /* __CURL_TYPECHECK_GCC_H */ diff --git a/deps/src/jsoncpp/LICENSE b/deps/src/jsoncpp/LICENSE new file mode 100644 index 000000000..ca2bfe1a0 --- /dev/null +++ b/deps/src/jsoncpp/LICENSE @@ -0,0 +1,55 @@ +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +The author (Baptiste Lepilleur) explicitly disclaims copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur, and is +released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. diff --git a/deps/src/jsoncpp/json/autolink.h b/deps/src/jsoncpp/json/autolink.h new file mode 100644 index 000000000..02328d1f1 --- /dev/null +++ b/deps/src/jsoncpp/json/autolink.h @@ -0,0 +1,24 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_AUTOLINK_H_INCLUDED +# define JSON_AUTOLINK_H_INCLUDED + +# include "config.h" + +# ifdef JSON_IN_CPPTL +# include +# endif + +# if !defined(JSON_NO_AUTOLINK) && !defined(JSON_DLL_BUILD) && !defined(JSON_IN_CPPTL) +# define CPPTL_AUTOLINK_NAME "json" +# undef CPPTL_AUTOLINK_DLL +# ifdef JSON_DLL +# define CPPTL_AUTOLINK_DLL +# endif +# include "autolink.h" +# endif + +#endif // JSON_AUTOLINK_H_INCLUDED diff --git a/deps/src/jsoncpp/json/config.h b/deps/src/jsoncpp/json/config.h new file mode 100644 index 000000000..7609d45e7 --- /dev/null +++ b/deps/src/jsoncpp/json/config.h @@ -0,0 +1,96 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_CONFIG_H_INCLUDED +# define JSON_CONFIG_H_INCLUDED + +/// If defined, indicates that json library is embedded in CppTL library. +//# define JSON_IN_CPPTL 1 + +/// If defined, indicates that json may leverage CppTL library +//# define JSON_USE_CPPTL 1 +/// If defined, indicates that cpptl vector based map should be used instead of std::map +/// as Value container. +//# define JSON_USE_CPPTL_SMALLMAP 1 +/// If defined, indicates that Json specific container should be used +/// (hash table & simple deque container with customizable allocator). +/// THIS FEATURE IS STILL EXPERIMENTAL! There is know bugs: See #3177332 +//# define JSON_VALUE_USE_INTERNAL_MAP 1 +/// Force usage of standard new/malloc based allocator instead of memory pool based allocator. +/// The memory pools allocator used optimization (initializing Value and ValueInternalLink +/// as if it was a POD) that may cause some validation tool to report errors. +/// Only has effects if JSON_VALUE_USE_INTERNAL_MAP is defined. +//# define JSON_USE_SIMPLE_INTERNAL_ALLOCATOR 1 + +/// If defined, indicates that Json use exception to report invalid type manipulation +/// instead of C assert macro. +# define JSON_USE_EXCEPTION 1 + +/// If defined, indicates that the source file is amalgated +/// to prevent private header inclusion. +/// Remarks: it is automatically defined in the generated amalgated header. +// #define JSON_IS_AMALGAMATION + + +# ifdef JSON_IN_CPPTL +# include +# ifndef JSON_USE_CPPTL +# define JSON_USE_CPPTL 1 +# endif +# endif + +# ifdef JSON_IN_CPPTL +# define JSON_API CPPTL_API +# elif defined(JSON_DLL_BUILD) +# define JSON_API __declspec(dllexport) +# elif defined(JSON_DLL) +# define JSON_API __declspec(dllimport) +# else +# define JSON_API +# endif + +// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for integer +// Storages, and 64 bits integer support is disabled. +// #define JSON_NO_INT64 1 + +#if defined(_MSC_VER) && _MSC_VER <= 1200 // MSVC 6 +// Microsoft Visual Studio 6 only support conversion from __int64 to double +// (no conversion from unsigned __int64). +#define JSON_USE_INT64_DOUBLE_CONVERSION 1 +#endif // if defined(_MSC_VER) && _MSC_VER < 1200 // MSVC 6 + +#if defined(_MSC_VER) && _MSC_VER >= 1500 // MSVC 2008 +/// Indicates that the following function is deprecated. +# define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +#endif + +#if !defined(JSONCPP_DEPRECATED) +# define JSONCPP_DEPRECATED(message) +#endif // if !defined(JSONCPP_DEPRECATED) + +namespace Json { + typedef int Int; + typedef unsigned int UInt; +# if defined(JSON_NO_INT64) + typedef int LargestInt; + typedef unsigned int LargestUInt; +# undef JSON_HAS_INT64 +# else // if defined(JSON_NO_INT64) + // For Microsoft Visual use specific types as long long is not supported +# if defined(_MSC_VER) // Microsoft Visual Studio + typedef __int64 Int64; + typedef unsigned __int64 UInt64; +# else // if defined(_MSC_VER) // Other platforms, use long long + typedef long long int Int64; + typedef unsigned long long int UInt64; +# endif // if defined(_MSC_VER) + typedef Int64 LargestInt; + typedef UInt64 LargestUInt; +# define JSON_HAS_INT64 +# endif // if defined(JSON_NO_INT64) +} // end namespace Json + + +#endif // JSON_CONFIG_H_INCLUDED diff --git a/deps/src/jsoncpp/json/features.h b/deps/src/jsoncpp/json/features.h new file mode 100644 index 000000000..435327844 --- /dev/null +++ b/deps/src/jsoncpp/json/features.h @@ -0,0 +1,49 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_FEATURES_H_INCLUDED +# define CPPTL_JSON_FEATURES_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +# include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + + /** \brief Configuration passed to reader and writer. + * This configuration object can be used to force the Reader or Writer + * to behave in a standard conforming way. + */ + class JSON_API Features + { + public: + /** \brief A configuration that allows all features and assumes all strings are UTF-8. + * - C & C++ comments are allowed + * - Root object can be any JSON value + * - Assumes Value strings are encoded in UTF-8 + */ + static Features all(); + + /** \brief A configuration that is strictly compatible with the JSON specification. + * - Comments are forbidden. + * - Root object must be either an array or an object value. + * - Assumes Value strings are encoded in UTF-8 + */ + static Features strictMode(); + + /** \brief Initialize the configuration like JsonConfig::allFeatures; + */ + Features(); + + /// \c true if comments are allowed. Default: \c true. + bool allowComments_; + + /// \c true if root must be either an array or an object value. Default: \c false. + bool strictRoot_; + }; + +} // namespace Json + +#endif // CPPTL_JSON_FEATURES_H_INCLUDED diff --git a/deps/src/jsoncpp/json/forwards.h b/deps/src/jsoncpp/json/forwards.h new file mode 100644 index 000000000..ab863da85 --- /dev/null +++ b/deps/src/jsoncpp/json/forwards.h @@ -0,0 +1,44 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_FORWARDS_H_INCLUDED +# define JSON_FORWARDS_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +# include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + + // writer.h + class FastWriter; + class StyledWriter; + + // reader.h + class Reader; + + // features.h + class Features; + + // value.h + typedef unsigned int ArrayIndex; + class StaticString; + class Path; + class PathArgument; + class Value; + class ValueIteratorBase; + class ValueIterator; + class ValueConstIterator; +#ifdef JSON_VALUE_USE_INTERNAL_MAP + class ValueMapAllocator; + class ValueInternalLink; + class ValueInternalArray; + class ValueInternalMap; +#endif // #ifdef JSON_VALUE_USE_INTERNAL_MAP + +} // namespace Json + + +#endif // JSON_FORWARDS_H_INCLUDED diff --git a/deps/src/jsoncpp/json/json.h b/deps/src/jsoncpp/json/json.h new file mode 100644 index 000000000..da5fc967e --- /dev/null +++ b/deps/src/jsoncpp/json/json.h @@ -0,0 +1,15 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_JSON_H_INCLUDED +# define JSON_JSON_H_INCLUDED + +# include "autolink.h" +# include "value.h" +# include "reader.h" +# include "writer.h" +# include "features.h" + +#endif // JSON_JSON_H_INCLUDED diff --git a/deps/src/jsoncpp/json/reader.h b/deps/src/jsoncpp/json/reader.h new file mode 100644 index 000000000..0a324dfc5 --- /dev/null +++ b/deps/src/jsoncpp/json/reader.h @@ -0,0 +1,214 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_READER_H_INCLUDED +# define CPPTL_JSON_READER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +# include "features.h" +# include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +# include +# include +# include +# include + +namespace Json { + + /** \brief Unserialize a JSON document into a Value. + * + */ + class JSON_API Reader + { + public: + typedef char Char; + typedef const Char *Location; + + /** \brief Constructs a Reader allowing all features + * for parsing. + */ + Reader(); + + /** \brief Constructs a Reader allowing the specified feature set + * for parsing. + */ + Reader( const Features &features ); + + /** \brief Read a Value from a JSON document. + * \param document UTF-8 encoded string containing the document to read. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param collectComments \c true to collect comment and allow writing them back during + * serialization, \c false to discard comments. + * This parameter is ignored if Features::allowComments_ + * is \c false. + * \return \c true if the document was successfully parsed, \c false if an error occurred. + */ + bool parse( const std::string &document, + Value &root, + bool collectComments = true ); + + /** \brief Read a Value from a JSON document. + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the document to read. + \ Must be >= beginDoc. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param collectComments \c true to collect comment and allow writing them back during + * serialization, \c false to discard comments. + * This parameter is ignored if Features::allowComments_ + * is \c false. + * \return \c true if the document was successfully parsed, \c false if an error occurred. + */ + bool parse( const char *beginDoc, const char *endDoc, + Value &root, + bool collectComments = true ); + + /// \brief Parse from input stream. + /// \see Json::operator>>(std::istream&, Json::Value&). + bool parse( std::istream &is, + Value &root, + bool collectComments = true ); + + /** \brief Returns a user friendly string that list errors in the parsed document. + * \return Formatted error message with the list of errors with their location in + * the parsed document. An empty string is returned if no error occurred + * during parsing. + * \deprecated Use getFormattedErrorMessages() instead (typo fix). + */ + JSONCPP_DEPRECATED("Use getFormattedErrorMessages instead") + std::string getFormatedErrorMessages() const; + + /** \brief Returns a user friendly string that list errors in the parsed document. + * \return Formatted error message with the list of errors with their location in + * the parsed document. An empty string is returned if no error occurred + * during parsing. + */ + std::string getFormattedErrorMessages() const; + + private: + enum TokenType + { + tokenEndOfStream = 0, + tokenObjectBegin, + tokenObjectEnd, + tokenArrayBegin, + tokenArrayEnd, + tokenString, + tokenNumber, + tokenTrue, + tokenFalse, + tokenNull, + tokenArraySeparator, + tokenMemberSeparator, + tokenComment, + tokenError + }; + + class Token + { + public: + TokenType type_; + Location start_; + Location end_; + }; + + class ErrorInfo + { + public: + Token token_; + std::string message_; + Location extra_; + }; + + typedef std::deque Errors; + + bool expectToken( TokenType type, Token &token, const char *message ); + bool readToken( Token &token ); + void skipSpaces(); + bool match( Location pattern, + int patternLength ); + bool readComment(); + bool readCStyleComment(); + bool readCppStyleComment(); + bool readString(); + void readNumber(); + bool readValue(); + bool readObject( Token &token ); + bool readArray( Token &token ); + bool decodeNumber( Token &token ); + bool decodeString( Token &token ); + bool decodeString( Token &token, std::string &decoded ); + bool decodeDouble( Token &token ); + bool decodeUnicodeCodePoint( Token &token, + Location ¤t, + Location end, + unsigned int &unicode ); + bool decodeUnicodeEscapeSequence( Token &token, + Location ¤t, + Location end, + unsigned int &unicode ); + bool addError( const std::string &message, + Token &token, + Location extra = 0 ); + bool recoverFromError( TokenType skipUntilToken ); + bool addErrorAndRecover( const std::string &message, + Token &token, + TokenType skipUntilToken ); + void skipUntilSpace(); + Value ¤tValue(); + Char getNextChar(); + void getLocationLineAndColumn( Location location, + int &line, + int &column ) const; + std::string getLocationLineAndColumn( Location location ) const; + void addComment( Location begin, + Location end, + CommentPlacement placement ); + void skipCommentTokens( Token &token ); + + typedef std::stack Nodes; + Nodes nodes_; + Errors errors_; + std::string document_; + Location begin_; + Location end_; + Location current_; + Location lastValueEnd_; + Value *lastValue_; + std::string commentsBefore_; + Features features_; + bool collectComments_; + }; + + /** \brief Read from 'sin' into 'root'. + + Always keep comments from the input JSON. + + This can be used to read a file into a particular sub-object. + For example: + \code + Json::Value root; + cin >> root["dir"]["file"]; + cout << root; + \endcode + Result: + \verbatim + { + "dir": { + "file": { + // The input stream JSON would be nested here. + } + } + } + \endverbatim + \throw std::exception on parse error. + \see Json::operator<<() + */ + std::istream& operator>>( std::istream&, Value& ); + +} // namespace Json + +#endif // CPPTL_JSON_READER_H_INCLUDED diff --git a/deps/src/jsoncpp/json/value.h b/deps/src/jsoncpp/json/value.h new file mode 100644 index 000000000..32e3455ec --- /dev/null +++ b/deps/src/jsoncpp/json/value.h @@ -0,0 +1,1103 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_H_INCLUDED +# define CPPTL_JSON_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +# include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +# include +# include + +# ifndef JSON_USE_CPPTL_SMALLMAP +# include +# else +# include +# endif +# ifdef JSON_USE_CPPTL +# include +# endif + +/** \brief JSON (JavaScript Object Notation). + */ +namespace Json { + + /** \brief Type of the value held by a Value object. + */ + enum ValueType + { + nullValue = 0, ///< 'null' value + intValue, ///< signed integer value + uintValue, ///< unsigned integer value + realValue, ///< double value + stringValue, ///< UTF-8 string value + booleanValue, ///< bool value + arrayValue, ///< array value (ordered list) + objectValue ///< object value (collection of name/value pairs). + }; + + enum CommentPlacement + { + commentBefore = 0, ///< a comment placed on the line before a value + commentAfterOnSameLine, ///< a comment just after a value on the same line + commentAfter, ///< a comment on the line after a value (only make sense for root value) + numberOfCommentPlacement + }; + +//# ifdef JSON_USE_CPPTL +// typedef CppTL::AnyEnumerator EnumMemberNames; +// typedef CppTL::AnyEnumerator EnumValues; +//# endif + + /** \brief Lightweight wrapper to tag static string. + * + * Value constructor and objectValue member assignement takes advantage of the + * StaticString and avoid the cost of string duplication when storing the + * string or the member name. + * + * Example of usage: + * \code + * Json::Value aValue( StaticString("some text") ); + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ + class JSON_API StaticString + { + public: + explicit StaticString( const char *czstring ) + : str_( czstring ) + { + } + + operator const char *() const + { + return str_; + } + + const char *c_str() const + { + return str_; + } + + private: + const char *str_; + }; + + /** \brief Represents a JSON value. + * + * This class is a discriminated union wrapper that can represents a: + * - signed integer [range: Value::minInt - Value::maxInt] + * - unsigned integer (range: 0 - Value::maxUInt) + * - double + * - UTF-8 string + * - boolean + * - 'null' + * - an ordered list of Value + * - collection of name/value pairs (javascript object) + * + * The type of the held value is represented by a #ValueType and + * can be obtained using type(). + * + * values of an #objectValue or #arrayValue can be accessed using operator[]() methods. + * Non const methods will automatically create the a #nullValue element + * if it does not exist. + * The sequence of an #arrayValue will be automatically resize and initialized + * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue. + * + * The get() methods can be used to obtanis default value in the case the required element + * does not exist. + * + * It is possible to iterate over the list of a #objectValue values using + * the getMemberNames() method. + */ + class JSON_API Value + { + friend class ValueIteratorBase; +# ifdef JSON_VALUE_USE_INTERNAL_MAP + friend class ValueInternalLink; + friend class ValueInternalMap; +# endif + public: + typedef std::vector Members; + typedef ValueIterator iterator; + typedef ValueConstIterator const_iterator; + typedef Json::UInt UInt; + typedef Json::Int Int; +# if defined(JSON_HAS_INT64) + typedef Json::UInt64 UInt64; + typedef Json::Int64 Int64; +#endif // defined(JSON_HAS_INT64) + typedef Json::LargestInt LargestInt; + typedef Json::LargestUInt LargestUInt; + typedef Json::ArrayIndex ArrayIndex; + + static const Value null; + /// Minimum signed integer value that can be stored in a Json::Value. + static const LargestInt minLargestInt; + /// Maximum signed integer value that can be stored in a Json::Value. + static const LargestInt maxLargestInt; + /// Maximum unsigned integer value that can be stored in a Json::Value. + static const LargestUInt maxLargestUInt; + + /// Minimum signed int value that can be stored in a Json::Value. + static const Int minInt; + /// Maximum signed int value that can be stored in a Json::Value. + static const Int maxInt; + /// Maximum unsigned int value that can be stored in a Json::Value. + static const UInt maxUInt; + + /// Minimum signed 64 bits int value that can be stored in a Json::Value. + static const Int64 minInt64; + /// Maximum signed 64 bits int value that can be stored in a Json::Value. + static const Int64 maxInt64; + /// Maximum unsigned 64 bits int value that can be stored in a Json::Value. + static const UInt64 maxUInt64; + + private: +#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION +# ifndef JSON_VALUE_USE_INTERNAL_MAP + class CZString + { + public: + enum DuplicationPolicy + { + noDuplication = 0, + duplicate, + duplicateOnCopy + }; + CZString( ArrayIndex index ); + CZString( const char *cstr, DuplicationPolicy allocate ); + CZString( const CZString &other ); + ~CZString(); + CZString &operator =( const CZString &other ); + bool operator<( const CZString &other ) const; + bool operator==( const CZString &other ) const; + ArrayIndex index() const; + const char *c_str() const; + bool isStaticString() const; + private: + void swap( CZString &other ); + const char *cstr_; + ArrayIndex index_; + }; + + public: +# ifndef JSON_USE_CPPTL_SMALLMAP + typedef std::map ObjectValues; +# else + typedef CppTL::SmallMap ObjectValues; +# endif // ifndef JSON_USE_CPPTL_SMALLMAP +# endif // ifndef JSON_VALUE_USE_INTERNAL_MAP +#endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + + public: + /** \brief Create a default Value of the given type. + + This is a very useful constructor. + To create an empty array, pass arrayValue. + To create an empty object, pass objectValue. + Another Value can then be set to this one by assignment. + This is useful since clear() and resize() will not alter types. + + Examples: + \code + Json::Value null_value; // null + Json::Value arr_value(Json::arrayValue); // [] + Json::Value obj_value(Json::objectValue); // {} + \endcode + */ + Value( ValueType type = nullValue ); + Value( Int value ); + Value( UInt value ); +#if defined(JSON_HAS_INT64) + Value( Int64 value ); + Value( UInt64 value ); +#endif // if defined(JSON_HAS_INT64) + Value( double value ); + Value( const char *value ); + Value( const char *beginValue, const char *endValue ); + /** \brief Constructs a value from a static string. + + * Like other value string constructor but do not duplicate the string for + * internal storage. The given string must remain alive after the call to this + * constructor. + * Example of usage: + * \code + * Json::Value aValue( StaticString("some text") ); + * \endcode + */ + Value( const StaticString &value ); + Value( const std::string &value ); +# ifdef JSON_USE_CPPTL + Value( const CppTL::ConstString &value ); +# endif + Value( bool value ); + Value( const Value &other ); + ~Value(); + + Value &operator=( const Value &other ); + /// Swap values. + /// \note Currently, comments are intentionally not swapped, for + /// both logic and efficiency. + void swap( Value &other ); + + ValueType type() const; + + bool operator <( const Value &other ) const; + bool operator <=( const Value &other ) const; + bool operator >=( const Value &other ) const; + bool operator >( const Value &other ) const; + + bool operator ==( const Value &other ) const; + bool operator !=( const Value &other ) const; + + int compare( const Value &other ) const; + + const char *asCString() const; + std::string asString() const; +# ifdef JSON_USE_CPPTL + CppTL::ConstString asConstString() const; +# endif + Int asInt() const; + UInt asUInt() const; + Int64 asInt64() const; + UInt64 asUInt64() const; + LargestInt asLargestInt() const; + LargestUInt asLargestUInt() const; + float asFloat() const; + double asDouble() const; + bool asBool() const; + + bool isNull() const; + bool isBool() const; + bool isInt() const; + bool isUInt() const; + bool isIntegral() const; + bool isDouble() const; + bool isNumeric() const; + bool isString() const; + bool isArray() const; + bool isObject() const; + + bool isConvertibleTo( ValueType other ) const; + + /// Number of values in array or object + ArrayIndex size() const; + + /// \brief Return true if empty array, empty object, or null; + /// otherwise, false. + bool empty() const; + + /// Return isNull() + bool operator!() const; + + /// Remove all object members and array elements. + /// \pre type() is arrayValue, objectValue, or nullValue + /// \post type() is unchanged + void clear(); + + /// Resize the array to size elements. + /// New elements are initialized to null. + /// May only be called on nullValue or arrayValue. + /// \pre type() is arrayValue or nullValue + /// \post type() is arrayValue + void resize( ArrayIndex size ); + + /// Access an array element (zero based index ). + /// If the array contains less than index element, then null value are inserted + /// in the array so that its size is index+1. + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + Value &operator[]( ArrayIndex index ); + + /// Access an array element (zero based index ). + /// If the array contains less than index element, then null value are inserted + /// in the array so that its size is index+1. + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + Value &operator[]( int index ); + + /// Access an array element (zero based index ) + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + const Value &operator[]( ArrayIndex index ) const; + + /// Access an array element (zero based index ) + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + const Value &operator[]( int index ) const; + + /// If the array contains at least index+1 elements, returns the element value, + /// otherwise returns defaultValue. + Value get( ArrayIndex index, + const Value &defaultValue ) const; + /// Return true if index < size(). + bool isValidIndex( ArrayIndex index ) const; + /// \brief Append value to array at the end. + /// + /// Equivalent to jsonvalue[jsonvalue.size()] = value; + Value &append( const Value &value ); + + /// Access an object value by name, create a null member if it does not exist. + Value &operator[]( const char *key ); + /// Access an object value by name, returns null if there is no member with that name. + const Value &operator[]( const char *key ) const; + /// Access an object value by name, create a null member if it does not exist. + Value &operator[]( const std::string &key ); + /// Access an object value by name, returns null if there is no member with that name. + const Value &operator[]( const std::string &key ) const; + /** \brief Access an object value by name, create a null member if it does not exist. + + * If the object as no entry for that name, then the member name used to store + * the new entry is not duplicated. + * Example of use: + * \code + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ + Value &operator[]( const StaticString &key ); +# ifdef JSON_USE_CPPTL + /// Access an object value by name, create a null member if it does not exist. + Value &operator[]( const CppTL::ConstString &key ); + /// Access an object value by name, returns null if there is no member with that name. + const Value &operator[]( const CppTL::ConstString &key ) const; +# endif + /// Return the member named key if it exist, defaultValue otherwise. + Value get( const char *key, + const Value &defaultValue ) const; + /// Return the member named key if it exist, defaultValue otherwise. + Value get( const std::string &key, + const Value &defaultValue ) const; +# ifdef JSON_USE_CPPTL + /// Return the member named key if it exist, defaultValue otherwise. + Value get( const CppTL::ConstString &key, + const Value &defaultValue ) const; +# endif + /// \brief Remove and return the named member. + /// + /// Do nothing if it did not exist. + /// \return the removed Value, or null. + /// \pre type() is objectValue or nullValue + /// \post type() is unchanged + Value removeMember( const char* key ); + /// Same as removeMember(const char*) + Value removeMember( const std::string &key ); + + /// Return true if the object has a member named key. + bool isMember( const char *key ) const; + /// Return true if the object has a member named key. + bool isMember( const std::string &key ) const; +# ifdef JSON_USE_CPPTL + /// Return true if the object has a member named key. + bool isMember( const CppTL::ConstString &key ) const; +# endif + + /// \brief Return a list of the member names. + /// + /// If null, return an empty list. + /// \pre type() is objectValue or nullValue + /// \post if type() was nullValue, it remains nullValue + Members getMemberNames() const; + +//# ifdef JSON_USE_CPPTL +// EnumMemberNames enumMemberNames() const; +// EnumValues enumValues() const; +//# endif + + /// Comments must be //... or /* ... */ + void setComment( const char *comment, + CommentPlacement placement ); + /// Comments must be //... or /* ... */ + void setComment( const std::string &comment, + CommentPlacement placement ); + bool hasComment( CommentPlacement placement ) const; + /// Include delimiters and embedded newlines. + std::string getComment( CommentPlacement placement ) const; + + std::string toStyledString() const; + + const_iterator begin() const; + const_iterator end() const; + + iterator begin(); + iterator end(); + + private: + Value &resolveReference( const char *key, + bool isStatic ); + +# ifdef JSON_VALUE_USE_INTERNAL_MAP + inline bool isItemAvailable() const + { + return itemIsUsed_ == 0; + } + + inline void setItemUsed( bool isUsed = true ) + { + itemIsUsed_ = isUsed ? 1 : 0; + } + + inline bool isMemberNameStatic() const + { + return memberNameIsStatic_ == 0; + } + + inline void setMemberNameIsStatic( bool isStatic ) + { + memberNameIsStatic_ = isStatic ? 1 : 0; + } +# endif // # ifdef JSON_VALUE_USE_INTERNAL_MAP + + private: + struct CommentInfo + { + CommentInfo(); + ~CommentInfo(); + + void setComment( const char *text ); + + char *comment_; + }; + + //struct MemberNamesTransform + //{ + // typedef const char *result_type; + // const char *operator()( const CZString &name ) const + // { + // return name.c_str(); + // } + //}; + + union ValueHolder + { + LargestInt int_; + LargestUInt uint_; + double real_; + bool bool_; + char *string_; +# ifdef JSON_VALUE_USE_INTERNAL_MAP + ValueInternalArray *array_; + ValueInternalMap *map_; +#else + ObjectValues *map_; +# endif + } value_; + ValueType type_ : 8; + int allocated_ : 1; // Notes: if declared as bool, bitfield is useless. +# ifdef JSON_VALUE_USE_INTERNAL_MAP + unsigned int itemIsUsed_ : 1; // used by the ValueInternalMap container. + int memberNameIsStatic_ : 1; // used by the ValueInternalMap container. +# endif + CommentInfo *comments_; + }; + + + /** \brief Experimental and untested: represents an element of the "path" to access a node. + */ + class PathArgument + { + public: + friend class Path; + + PathArgument(); + PathArgument( ArrayIndex index ); + PathArgument( const char *key ); + PathArgument( const std::string &key ); + + private: + enum Kind + { + kindNone = 0, + kindIndex, + kindKey + }; + std::string key_; + ArrayIndex index_; + Kind kind_; + }; + + /** \brief Experimental and untested: represents a "path" to access a node. + * + * Syntax: + * - "." => root node + * - ".[n]" => elements at index 'n' of root node (an array value) + * - ".name" => member named 'name' of root node (an object value) + * - ".name1.name2.name3" + * - ".[0][1][2].name1[3]" + * - ".%" => member name is provided as parameter + * - ".[%]" => index is provied as parameter + */ + class Path + { + public: + Path( const std::string &path, + const PathArgument &a1 = PathArgument(), + const PathArgument &a2 = PathArgument(), + const PathArgument &a3 = PathArgument(), + const PathArgument &a4 = PathArgument(), + const PathArgument &a5 = PathArgument() ); + + const Value &resolve( const Value &root ) const; + Value resolve( const Value &root, + const Value &defaultValue ) const; + /// Creates the "path" to access the specified node and returns a reference on the node. + Value &make( Value &root ) const; + + private: + typedef std::vector InArgs; + typedef std::vector Args; + + void makePath( const std::string &path, + const InArgs &in ); + void addPathInArg( const std::string &path, + const InArgs &in, + InArgs::const_iterator &itInArg, + PathArgument::Kind kind ); + void invalidPath( const std::string &path, + int location ); + + Args args_; + }; + + + +#ifdef JSON_VALUE_USE_INTERNAL_MAP + /** \brief Allocator to customize Value internal map. + * Below is an example of a simple implementation (default implementation actually + * use memory pool for speed). + * \code + class DefaultValueMapAllocator : public ValueMapAllocator + { + public: // overridden from ValueMapAllocator + virtual ValueInternalMap *newMap() + { + return new ValueInternalMap(); + } + + virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) + { + return new ValueInternalMap( other ); + } + + virtual void destructMap( ValueInternalMap *map ) + { + delete map; + } + + virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) + { + return new ValueInternalLink[size]; + } + + virtual void releaseMapBuckets( ValueInternalLink *links ) + { + delete [] links; + } + + virtual ValueInternalLink *allocateMapLink() + { + return new ValueInternalLink(); + } + + virtual void releaseMapLink( ValueInternalLink *link ) + { + delete link; + } + }; + * \endcode + */ + class JSON_API ValueMapAllocator + { + public: + virtual ~ValueMapAllocator(); + virtual ValueInternalMap *newMap() = 0; + virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) = 0; + virtual void destructMap( ValueInternalMap *map ) = 0; + virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) = 0; + virtual void releaseMapBuckets( ValueInternalLink *links ) = 0; + virtual ValueInternalLink *allocateMapLink() = 0; + virtual void releaseMapLink( ValueInternalLink *link ) = 0; + }; + + /** \brief ValueInternalMap hash-map bucket chain link (for internal use only). + * \internal previous_ & next_ allows for bidirectional traversal. + */ + class JSON_API ValueInternalLink + { + public: + enum { itemPerLink = 6 }; // sizeof(ValueInternalLink) = 128 on 32 bits architecture. + enum InternalFlags { + flagAvailable = 0, + flagUsed = 1 + }; + + ValueInternalLink(); + + ~ValueInternalLink(); + + Value items_[itemPerLink]; + char *keys_[itemPerLink]; + ValueInternalLink *previous_; + ValueInternalLink *next_; + }; + + + /** \brief A linked page based hash-table implementation used internally by Value. + * \internal ValueInternalMap is a tradional bucket based hash-table, with a linked + * list in each bucket to handle collision. There is an addional twist in that + * each node of the collision linked list is a page containing a fixed amount of + * value. This provides a better compromise between memory usage and speed. + * + * Each bucket is made up of a chained list of ValueInternalLink. The last + * link of a given bucket can be found in the 'previous_' field of the following bucket. + * The last link of the last bucket is stored in tailLink_ as it has no following bucket. + * Only the last link of a bucket may contains 'available' item. The last link always + * contains at least one element unless is it the bucket one very first link. + */ + class JSON_API ValueInternalMap + { + friend class ValueIteratorBase; + friend class Value; + public: + typedef unsigned int HashKey; + typedef unsigned int BucketIndex; + +# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + struct IteratorState + { + IteratorState() + : map_(0) + , link_(0) + , itemIndex_(0) + , bucketIndex_(0) + { + } + ValueInternalMap *map_; + ValueInternalLink *link_; + BucketIndex itemIndex_; + BucketIndex bucketIndex_; + }; +# endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + + ValueInternalMap(); + ValueInternalMap( const ValueInternalMap &other ); + ValueInternalMap &operator =( const ValueInternalMap &other ); + ~ValueInternalMap(); + + void swap( ValueInternalMap &other ); + + BucketIndex size() const; + + void clear(); + + bool reserveDelta( BucketIndex growth ); + + bool reserve( BucketIndex newItemCount ); + + const Value *find( const char *key ) const; + + Value *find( const char *key ); + + Value &resolveReference( const char *key, + bool isStatic ); + + void remove( const char *key ); + + void doActualRemove( ValueInternalLink *link, + BucketIndex index, + BucketIndex bucketIndex ); + + ValueInternalLink *&getLastLinkInBucket( BucketIndex bucketIndex ); + + Value &setNewItem( const char *key, + bool isStatic, + ValueInternalLink *link, + BucketIndex index ); + + Value &unsafeAdd( const char *key, + bool isStatic, + HashKey hashedKey ); + + HashKey hash( const char *key ) const; + + int compare( const ValueInternalMap &other ) const; + + private: + void makeBeginIterator( IteratorState &it ) const; + void makeEndIterator( IteratorState &it ) const; + static bool equals( const IteratorState &x, const IteratorState &other ); + static void increment( IteratorState &iterator ); + static void incrementBucket( IteratorState &iterator ); + static void decrement( IteratorState &iterator ); + static const char *key( const IteratorState &iterator ); + static const char *key( const IteratorState &iterator, bool &isStatic ); + static Value &value( const IteratorState &iterator ); + static int distance( const IteratorState &x, const IteratorState &y ); + + private: + ValueInternalLink *buckets_; + ValueInternalLink *tailLink_; + BucketIndex bucketsSize_; + BucketIndex itemCount_; + }; + + /** \brief A simplified deque implementation used internally by Value. + * \internal + * It is based on a list of fixed "page", each page contains a fixed number of items. + * Instead of using a linked-list, a array of pointer is used for fast item look-up. + * Look-up for an element is as follow: + * - compute page index: pageIndex = itemIndex / itemsPerPage + * - look-up item in page: pages_[pageIndex][itemIndex % itemsPerPage] + * + * Insertion is amortized constant time (only the array containing the index of pointers + * need to be reallocated when items are appended). + */ + class JSON_API ValueInternalArray + { + friend class Value; + friend class ValueIteratorBase; + public: + enum { itemsPerPage = 8 }; // should be a power of 2 for fast divide and modulo. + typedef Value::ArrayIndex ArrayIndex; + typedef unsigned int PageIndex; + +# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + struct IteratorState // Must be a POD + { + IteratorState() + : array_(0) + , currentPageIndex_(0) + , currentItemIndex_(0) + { + } + ValueInternalArray *array_; + Value **currentPageIndex_; + unsigned int currentItemIndex_; + }; +# endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + + ValueInternalArray(); + ValueInternalArray( const ValueInternalArray &other ); + ValueInternalArray &operator =( const ValueInternalArray &other ); + ~ValueInternalArray(); + void swap( ValueInternalArray &other ); + + void clear(); + void resize( ArrayIndex newSize ); + + Value &resolveReference( ArrayIndex index ); + + Value *find( ArrayIndex index ) const; + + ArrayIndex size() const; + + int compare( const ValueInternalArray &other ) const; + + private: + static bool equals( const IteratorState &x, const IteratorState &other ); + static void increment( IteratorState &iterator ); + static void decrement( IteratorState &iterator ); + static Value &dereference( const IteratorState &iterator ); + static Value &unsafeDereference( const IteratorState &iterator ); + static int distance( const IteratorState &x, const IteratorState &y ); + static ArrayIndex indexOf( const IteratorState &iterator ); + void makeBeginIterator( IteratorState &it ) const; + void makeEndIterator( IteratorState &it ) const; + void makeIterator( IteratorState &it, ArrayIndex index ) const; + + void makeIndexValid( ArrayIndex index ); + + Value **pages_; + ArrayIndex size_; + PageIndex pageCount_; + }; + + /** \brief Experimental: do not use. Allocator to customize Value internal array. + * Below is an example of a simple implementation (actual implementation use + * memory pool). + \code +class DefaultValueArrayAllocator : public ValueArrayAllocator +{ +public: // overridden from ValueArrayAllocator + virtual ~DefaultValueArrayAllocator() + { + } + + virtual ValueInternalArray *newArray() + { + return new ValueInternalArray(); + } + + virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) + { + return new ValueInternalArray( other ); + } + + virtual void destruct( ValueInternalArray *array ) + { + delete array; + } + + virtual void reallocateArrayPageIndex( Value **&indexes, + ValueInternalArray::PageIndex &indexCount, + ValueInternalArray::PageIndex minNewIndexCount ) + { + ValueInternalArray::PageIndex newIndexCount = (indexCount*3)/2 + 1; + if ( minNewIndexCount > newIndexCount ) + newIndexCount = minNewIndexCount; + void *newIndexes = realloc( indexes, sizeof(Value*) * newIndexCount ); + if ( !newIndexes ) + throw std::bad_alloc(); + indexCount = newIndexCount; + indexes = static_cast( newIndexes ); + } + virtual void releaseArrayPageIndex( Value **indexes, + ValueInternalArray::PageIndex indexCount ) + { + if ( indexes ) + free( indexes ); + } + + virtual Value *allocateArrayPage() + { + return static_cast( malloc( sizeof(Value) * ValueInternalArray::itemsPerPage ) ); + } + + virtual void releaseArrayPage( Value *value ) + { + if ( value ) + free( value ); + } +}; + \endcode + */ + class JSON_API ValueArrayAllocator + { + public: + virtual ~ValueArrayAllocator(); + virtual ValueInternalArray *newArray() = 0; + virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) = 0; + virtual void destructArray( ValueInternalArray *array ) = 0; + /** \brief Reallocate array page index. + * Reallocates an array of pointer on each page. + * \param indexes [input] pointer on the current index. May be \c NULL. + * [output] pointer on the new index of at least + * \a minNewIndexCount pages. + * \param indexCount [input] current number of pages in the index. + * [output] number of page the reallocated index can handle. + * \b MUST be >= \a minNewIndexCount. + * \param minNewIndexCount Minimum number of page the new index must be able to + * handle. + */ + virtual void reallocateArrayPageIndex( Value **&indexes, + ValueInternalArray::PageIndex &indexCount, + ValueInternalArray::PageIndex minNewIndexCount ) = 0; + virtual void releaseArrayPageIndex( Value **indexes, + ValueInternalArray::PageIndex indexCount ) = 0; + virtual Value *allocateArrayPage() = 0; + virtual void releaseArrayPage( Value *value ) = 0; + }; +#endif // #ifdef JSON_VALUE_USE_INTERNAL_MAP + + + /** \brief base class for Value iterators. + * + */ + class ValueIteratorBase + { + public: + typedef unsigned int size_t; + typedef int difference_type; + typedef ValueIteratorBase SelfType; + + ValueIteratorBase(); +#ifndef JSON_VALUE_USE_INTERNAL_MAP + explicit ValueIteratorBase( const Value::ObjectValues::iterator ¤t ); +#else + ValueIteratorBase( const ValueInternalArray::IteratorState &state ); + ValueIteratorBase( const ValueInternalMap::IteratorState &state ); +#endif + + bool operator ==( const SelfType &other ) const + { + return isEqual( other ); + } + + bool operator !=( const SelfType &other ) const + { + return !isEqual( other ); + } + + difference_type operator -( const SelfType &other ) const + { + return computeDistance( other ); + } + + /// Return either the index or the member name of the referenced value as a Value. + Value key() const; + + /// Return the index of the referenced Value. -1 if it is not an arrayValue. + UInt index() const; + + /// Return the member name of the referenced Value. "" if it is not an objectValue. + const char *memberName() const; + + protected: + Value &deref() const; + + void increment(); + + void decrement(); + + difference_type computeDistance( const SelfType &other ) const; + + bool isEqual( const SelfType &other ) const; + + void copy( const SelfType &other ); + + private: +#ifndef JSON_VALUE_USE_INTERNAL_MAP + Value::ObjectValues::iterator current_; + // Indicates that iterator is for a null value. + bool isNull_; +#else + union + { + ValueInternalArray::IteratorState array_; + ValueInternalMap::IteratorState map_; + } iterator_; + bool isArray_; +#endif + }; + + /** \brief const iterator for object and array value. + * + */ + class ValueConstIterator : public ValueIteratorBase + { + friend class Value; + public: + typedef unsigned int size_t; + typedef int difference_type; + typedef const Value &reference; + typedef const Value *pointer; + typedef ValueConstIterator SelfType; + + ValueConstIterator(); + private: + /*! \internal Use by Value to create an iterator. + */ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + explicit ValueConstIterator( const Value::ObjectValues::iterator ¤t ); +#else + ValueConstIterator( const ValueInternalArray::IteratorState &state ); + ValueConstIterator( const ValueInternalMap::IteratorState &state ); +#endif + public: + SelfType &operator =( const ValueIteratorBase &other ); + + SelfType operator++( int ) + { + SelfType temp( *this ); + ++*this; + return temp; + } + + SelfType operator--( int ) + { + SelfType temp( *this ); + --*this; + return temp; + } + + SelfType &operator--() + { + decrement(); + return *this; + } + + SelfType &operator++() + { + increment(); + return *this; + } + + reference operator *() const + { + return deref(); + } + }; + + + /** \brief Iterator for object and array value. + */ + class ValueIterator : public ValueIteratorBase + { + friend class Value; + public: + typedef unsigned int size_t; + typedef int difference_type; + typedef Value &reference; + typedef Value *pointer; + typedef ValueIterator SelfType; + + ValueIterator(); + ValueIterator( const ValueConstIterator &other ); + ValueIterator( const ValueIterator &other ); + private: + /*! \internal Use by Value to create an iterator. + */ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + explicit ValueIterator( const Value::ObjectValues::iterator ¤t ); +#else + ValueIterator( const ValueInternalArray::IteratorState &state ); + ValueIterator( const ValueInternalMap::IteratorState &state ); +#endif + public: + + SelfType &operator =( const SelfType &other ); + + SelfType operator++( int ) + { + SelfType temp( *this ); + ++*this; + return temp; + } + + SelfType operator--( int ) + { + SelfType temp( *this ); + --*this; + return temp; + } + + SelfType &operator--() + { + decrement(); + return *this; + } + + SelfType &operator++() + { + increment(); + return *this; + } + + reference operator *() const + { + return deref(); + } + }; + + +} // namespace Json + + +#endif // CPPTL_JSON_H_INCLUDED diff --git a/deps/src/jsoncpp/json/writer.h b/deps/src/jsoncpp/json/writer.h new file mode 100644 index 000000000..478936365 --- /dev/null +++ b/deps/src/jsoncpp/json/writer.h @@ -0,0 +1,185 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_WRITER_H_INCLUDED +# define JSON_WRITER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +# include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +# include +# include +# include + +namespace Json { + + class Value; + + /** \brief Abstract class for writers. + */ + class JSON_API Writer + { + public: + virtual ~Writer(); + + virtual std::string write( const Value &root ) = 0; + }; + + /** \brief Outputs a Value in JSON format without formatting (not human friendly). + * + * The JSON document is written in a single line. It is not intended for 'human' consumption, + * but may be usefull to support feature such as RPC where bandwith is limited. + * \sa Reader, Value + */ + class JSON_API FastWriter : public Writer + { + public: + FastWriter(); + virtual ~FastWriter(){} + + void enableYAMLCompatibility(); + + public: // overridden from Writer + virtual std::string write( const Value &root ); + + private: + void writeValue( const Value &value ); + + std::string document_; + bool yamlCompatiblityEnabled_; + }; + + /** \brief Writes a Value in JSON format in a human friendly way. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value types, + * and all the values fit on one lines, then print the array on a single line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their #CommentPlacement. + * + * \sa Reader, Value, Value::setComment() + */ + class JSON_API StyledWriter: public Writer + { + public: + StyledWriter(); + virtual ~StyledWriter(){} + + public: // overridden from Writer + /** \brief Serialize a Value in JSON format. + * \param root Value to serialize. + * \return String containing the JSON document that represents the root value. + */ + virtual std::string write( const Value &root ); + + private: + void writeValue( const Value &value ); + void writeArrayValue( const Value &value ); + bool isMultineArray( const Value &value ); + void pushValue( const std::string &value ); + void writeIndent(); + void writeWithIndent( const std::string &value ); + void indent(); + void unindent(); + void writeCommentBeforeValue( const Value &root ); + void writeCommentAfterValueOnSameLine( const Value &root ); + bool hasCommentForValue( const Value &value ); + static std::string normalizeEOL( const std::string &text ); + + typedef std::vector ChildValues; + + ChildValues childValues_; + std::string document_; + std::string indentString_; + int rightMargin_; + int indentSize_; + bool addChildValues_; + }; + + /** \brief Writes a Value in JSON format in a human friendly way, + to a stream rather than to a string. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value types, + * and all the values fit on one lines, then print the array on a single line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their #CommentPlacement. + * + * \param indentation Each level will be indented by this amount extra. + * \sa Reader, Value, Value::setComment() + */ + class JSON_API StyledStreamWriter + { + public: + StyledStreamWriter( std::string indentation="\t" ); + ~StyledStreamWriter(){} + + public: + /** \brief Serialize a Value in JSON format. + * \param out Stream to write to. (Can be ostringstream, e.g.) + * \param root Value to serialize. + * \note There is no point in deriving from Writer, since write() should not return a value. + */ + void write( std::ostream &out, const Value &root ); + + private: + void writeValue( const Value &value ); + void writeArrayValue( const Value &value ); + bool isMultineArray( const Value &value ); + void pushValue( const std::string &value ); + void writeIndent(); + void writeWithIndent( const std::string &value ); + void indent(); + void unindent(); + void writeCommentBeforeValue( const Value &root ); + void writeCommentAfterValueOnSameLine( const Value &root ); + bool hasCommentForValue( const Value &value ); + static std::string normalizeEOL( const std::string &text ); + + typedef std::vector ChildValues; + + ChildValues childValues_; + std::ostream* document_; + std::string indentString_; + int rightMargin_; + std::string indentation_; + bool addChildValues_; + }; + +# if defined(JSON_HAS_INT64) + std::string JSON_API valueToString( Int value ); + std::string JSON_API valueToString( UInt value ); +# endif // if defined(JSON_HAS_INT64) + std::string JSON_API valueToString( LargestInt value ); + std::string JSON_API valueToString( LargestUInt value ); + std::string JSON_API valueToString( double value ); + std::string JSON_API valueToString( bool value ); + std::string JSON_API valueToQuotedString( const char *value ); + + /// \brief Output using the StyledStreamWriter. + /// \see Json::operator>>() + std::ostream& operator<<( std::ostream&, const Value &root ); + +} // namespace Json + + + +#endif // JSON_WRITER_H_INCLUDED diff --git a/deps/src/jsoncpp/json_batchallocator.h b/deps/src/jsoncpp/json_batchallocator.h new file mode 100644 index 000000000..173e2ed25 --- /dev/null +++ b/deps/src/jsoncpp/json_batchallocator.h @@ -0,0 +1,130 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSONCPP_BATCHALLOCATOR_H_INCLUDED +# define JSONCPP_BATCHALLOCATOR_H_INCLUDED + +# include +# include + +# ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + +namespace Json { + +/* Fast memory allocator. + * + * This memory allocator allocates memory for a batch of object (specified by + * the page size, the number of object in each page). + * + * It does not allow the destruction of a single object. All the allocated objects + * can be destroyed at once. The memory can be either released or reused for future + * allocation. + * + * The in-place new operator must be used to construct the object using the pointer + * returned by allocate. + */ +template +class BatchAllocator +{ +public: + typedef AllocatedType Type; + + BatchAllocator( unsigned int objectsPerPage = 255 ) + : freeHead_( 0 ) + , objectsPerPage_( objectsPerPage ) + { +// printf( "Size: %d => %s\n", sizeof(AllocatedType), typeid(AllocatedType).name() ); + assert( sizeof(AllocatedType) * objectPerAllocation >= sizeof(AllocatedType *) ); // We must be able to store a slist in the object free space. + assert( objectsPerPage >= 16 ); + batches_ = allocateBatch( 0 ); // allocated a dummy page + currentBatch_ = batches_; + } + + ~BatchAllocator() + { + for ( BatchInfo *batch = batches_; batch; ) + { + BatchInfo *nextBatch = batch->next_; + free( batch ); + batch = nextBatch; + } + } + + /// allocate space for an array of objectPerAllocation object. + /// @warning it is the responsability of the caller to call objects constructors. + AllocatedType *allocate() + { + if ( freeHead_ ) // returns node from free list. + { + AllocatedType *object = freeHead_; + freeHead_ = *(AllocatedType **)object; + return object; + } + if ( currentBatch_->used_ == currentBatch_->end_ ) + { + currentBatch_ = currentBatch_->next_; + while ( currentBatch_ && currentBatch_->used_ == currentBatch_->end_ ) + currentBatch_ = currentBatch_->next_; + + if ( !currentBatch_ ) // no free batch found, allocate a new one + { + currentBatch_ = allocateBatch( objectsPerPage_ ); + currentBatch_->next_ = batches_; // insert at the head of the list + batches_ = currentBatch_; + } + } + AllocatedType *allocated = currentBatch_->used_; + currentBatch_->used_ += objectPerAllocation; + return allocated; + } + + /// Release the object. + /// @warning it is the responsability of the caller to actually destruct the object. + void release( AllocatedType *object ) + { + assert( object != 0 ); + *(AllocatedType **)object = freeHead_; + freeHead_ = object; + } + +private: + struct BatchInfo + { + BatchInfo *next_; + AllocatedType *used_; + AllocatedType *end_; + AllocatedType buffer_[objectPerAllocation]; + }; + + // disabled copy constructor and assignement operator. + BatchAllocator( const BatchAllocator & ); + void operator =( const BatchAllocator &); + + static BatchInfo *allocateBatch( unsigned int objectsPerPage ) + { + const unsigned int mallocSize = sizeof(BatchInfo) - sizeof(AllocatedType)* objectPerAllocation + + sizeof(AllocatedType) * objectPerAllocation * objectsPerPage; + BatchInfo *batch = static_cast( malloc( mallocSize ) ); + batch->next_ = 0; + batch->used_ = batch->buffer_; + batch->end_ = batch->buffer_ + objectsPerPage; + return batch; + } + + BatchInfo *batches_; + BatchInfo *currentBatch_; + /// Head of a single linked list within the allocated space of freeed object + AllocatedType *freeHead_; + unsigned int objectsPerPage_; +}; + + +} // namespace Json + +# endif // ifndef JSONCPP_DOC_INCLUDE_IMPLEMENTATION + +#endif // JSONCPP_BATCHALLOCATOR_H_INCLUDED + diff --git a/deps/src/jsoncpp/json_internalarray.inl b/deps/src/jsoncpp/json_internalarray.inl new file mode 100644 index 000000000..3a532ad75 --- /dev/null +++ b/deps/src/jsoncpp/json_internalarray.inl @@ -0,0 +1,456 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +// included by json_value.cpp + +namespace Json { + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueInternalArray +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueArrayAllocator::~ValueArrayAllocator() +{ +} + +// ////////////////////////////////////////////////////////////////// +// class DefaultValueArrayAllocator +// ////////////////////////////////////////////////////////////////// +#ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR +class DefaultValueArrayAllocator : public ValueArrayAllocator +{ +public: // overridden from ValueArrayAllocator + virtual ~DefaultValueArrayAllocator() + { + } + + virtual ValueInternalArray *newArray() + { + return new ValueInternalArray(); + } + + virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) + { + return new ValueInternalArray( other ); + } + + virtual void destructArray( ValueInternalArray *array ) + { + delete array; + } + + virtual void reallocateArrayPageIndex( Value **&indexes, + ValueInternalArray::PageIndex &indexCount, + ValueInternalArray::PageIndex minNewIndexCount ) + { + ValueInternalArray::PageIndex newIndexCount = (indexCount*3)/2 + 1; + if ( minNewIndexCount > newIndexCount ) + newIndexCount = minNewIndexCount; + void *newIndexes = realloc( indexes, sizeof(Value*) * newIndexCount ); + if ( !newIndexes ) + throw std::bad_alloc(); + indexCount = newIndexCount; + indexes = static_cast( newIndexes ); + } + virtual void releaseArrayPageIndex( Value **indexes, + ValueInternalArray::PageIndex indexCount ) + { + if ( indexes ) + free( indexes ); + } + + virtual Value *allocateArrayPage() + { + return static_cast( malloc( sizeof(Value) * ValueInternalArray::itemsPerPage ) ); + } + + virtual void releaseArrayPage( Value *value ) + { + if ( value ) + free( value ); + } +}; + +#else // #ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR +/// @todo make this thread-safe (lock when accessign batch allocator) +class DefaultValueArrayAllocator : public ValueArrayAllocator +{ +public: // overridden from ValueArrayAllocator + virtual ~DefaultValueArrayAllocator() + { + } + + virtual ValueInternalArray *newArray() + { + ValueInternalArray *array = arraysAllocator_.allocate(); + new (array) ValueInternalArray(); // placement new + return array; + } + + virtual ValueInternalArray *newArrayCopy( const ValueInternalArray &other ) + { + ValueInternalArray *array = arraysAllocator_.allocate(); + new (array) ValueInternalArray( other ); // placement new + return array; + } + + virtual void destructArray( ValueInternalArray *array ) + { + if ( array ) + { + array->~ValueInternalArray(); + arraysAllocator_.release( array ); + } + } + + virtual void reallocateArrayPageIndex( Value **&indexes, + ValueInternalArray::PageIndex &indexCount, + ValueInternalArray::PageIndex minNewIndexCount ) + { + ValueInternalArray::PageIndex newIndexCount = (indexCount*3)/2 + 1; + if ( minNewIndexCount > newIndexCount ) + newIndexCount = minNewIndexCount; + void *newIndexes = realloc( indexes, sizeof(Value*) * newIndexCount ); + if ( !newIndexes ) + throw std::bad_alloc(); + indexCount = newIndexCount; + indexes = static_cast( newIndexes ); + } + virtual void releaseArrayPageIndex( Value **indexes, + ValueInternalArray::PageIndex indexCount ) + { + if ( indexes ) + free( indexes ); + } + + virtual Value *allocateArrayPage() + { + return static_cast( pagesAllocator_.allocate() ); + } + + virtual void releaseArrayPage( Value *value ) + { + if ( value ) + pagesAllocator_.release( value ); + } +private: + BatchAllocator arraysAllocator_; + BatchAllocator pagesAllocator_; +}; +#endif // #ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR + +static ValueArrayAllocator *&arrayAllocator() +{ + static DefaultValueArrayAllocator defaultAllocator; + static ValueArrayAllocator *arrayAllocator = &defaultAllocator; + return arrayAllocator; +} + +static struct DummyArrayAllocatorInitializer { + DummyArrayAllocatorInitializer() + { + arrayAllocator(); // ensure arrayAllocator() statics are initialized before main(). + } +} dummyArrayAllocatorInitializer; + +// ////////////////////////////////////////////////////////////////// +// class ValueInternalArray +// ////////////////////////////////////////////////////////////////// +bool +ValueInternalArray::equals( const IteratorState &x, + const IteratorState &other ) +{ + return x.array_ == other.array_ + && x.currentItemIndex_ == other.currentItemIndex_ + && x.currentPageIndex_ == other.currentPageIndex_; +} + + +void +ValueInternalArray::increment( IteratorState &it ) +{ + JSON_ASSERT_MESSAGE( it.array_ && + (it.currentPageIndex_ - it.array_->pages_)*itemsPerPage + it.currentItemIndex_ + != it.array_->size_, + "ValueInternalArray::increment(): moving iterator beyond end" ); + ++(it.currentItemIndex_); + if ( it.currentItemIndex_ == itemsPerPage ) + { + it.currentItemIndex_ = 0; + ++(it.currentPageIndex_); + } +} + + +void +ValueInternalArray::decrement( IteratorState &it ) +{ + JSON_ASSERT_MESSAGE( it.array_ && it.currentPageIndex_ == it.array_->pages_ + && it.currentItemIndex_ == 0, + "ValueInternalArray::decrement(): moving iterator beyond end" ); + if ( it.currentItemIndex_ == 0 ) + { + it.currentItemIndex_ = itemsPerPage-1; + --(it.currentPageIndex_); + } + else + { + --(it.currentItemIndex_); + } +} + + +Value & +ValueInternalArray::unsafeDereference( const IteratorState &it ) +{ + return (*(it.currentPageIndex_))[it.currentItemIndex_]; +} + + +Value & +ValueInternalArray::dereference( const IteratorState &it ) +{ + JSON_ASSERT_MESSAGE( it.array_ && + (it.currentPageIndex_ - it.array_->pages_)*itemsPerPage + it.currentItemIndex_ + < it.array_->size_, + "ValueInternalArray::dereference(): dereferencing invalid iterator" ); + return unsafeDereference( it ); +} + +void +ValueInternalArray::makeBeginIterator( IteratorState &it ) const +{ + it.array_ = const_cast( this ); + it.currentItemIndex_ = 0; + it.currentPageIndex_ = pages_; +} + + +void +ValueInternalArray::makeIterator( IteratorState &it, ArrayIndex index ) const +{ + it.array_ = const_cast( this ); + it.currentItemIndex_ = index % itemsPerPage; + it.currentPageIndex_ = pages_ + index / itemsPerPage; +} + + +void +ValueInternalArray::makeEndIterator( IteratorState &it ) const +{ + makeIterator( it, size_ ); +} + + +ValueInternalArray::ValueInternalArray() + : pages_( 0 ) + , size_( 0 ) + , pageCount_( 0 ) +{ +} + + +ValueInternalArray::ValueInternalArray( const ValueInternalArray &other ) + : pages_( 0 ) + , pageCount_( 0 ) + , size_( other.size_ ) +{ + PageIndex minNewPages = other.size_ / itemsPerPage; + arrayAllocator()->reallocateArrayPageIndex( pages_, pageCount_, minNewPages ); + JSON_ASSERT_MESSAGE( pageCount_ >= minNewPages, + "ValueInternalArray::reserve(): bad reallocation" ); + IteratorState itOther; + other.makeBeginIterator( itOther ); + Value *value; + for ( ArrayIndex index = 0; index < size_; ++index, increment(itOther) ) + { + if ( index % itemsPerPage == 0 ) + { + PageIndex pageIndex = index / itemsPerPage; + value = arrayAllocator()->allocateArrayPage(); + pages_[pageIndex] = value; + } + new (value) Value( dereference( itOther ) ); + } +} + + +ValueInternalArray & +ValueInternalArray::operator =( const ValueInternalArray &other ) +{ + ValueInternalArray temp( other ); + swap( temp ); + return *this; +} + + +ValueInternalArray::~ValueInternalArray() +{ + // destroy all constructed items + IteratorState it; + IteratorState itEnd; + makeBeginIterator( it); + makeEndIterator( itEnd ); + for ( ; !equals(it,itEnd); increment(it) ) + { + Value *value = &dereference(it); + value->~Value(); + } + // release all pages + PageIndex lastPageIndex = size_ / itemsPerPage; + for ( PageIndex pageIndex = 0; pageIndex < lastPageIndex; ++pageIndex ) + arrayAllocator()->releaseArrayPage( pages_[pageIndex] ); + // release pages index + arrayAllocator()->releaseArrayPageIndex( pages_, pageCount_ ); +} + + +void +ValueInternalArray::swap( ValueInternalArray &other ) +{ + Value **tempPages = pages_; + pages_ = other.pages_; + other.pages_ = tempPages; + ArrayIndex tempSize = size_; + size_ = other.size_; + other.size_ = tempSize; + PageIndex tempPageCount = pageCount_; + pageCount_ = other.pageCount_; + other.pageCount_ = tempPageCount; +} + +void +ValueInternalArray::clear() +{ + ValueInternalArray dummy; + swap( dummy ); +} + + +void +ValueInternalArray::resize( ArrayIndex newSize ) +{ + if ( newSize == 0 ) + clear(); + else if ( newSize < size_ ) + { + IteratorState it; + IteratorState itEnd; + makeIterator( it, newSize ); + makeIterator( itEnd, size_ ); + for ( ; !equals(it,itEnd); increment(it) ) + { + Value *value = &dereference(it); + value->~Value(); + } + PageIndex pageIndex = (newSize + itemsPerPage - 1) / itemsPerPage; + PageIndex lastPageIndex = size_ / itemsPerPage; + for ( ; pageIndex < lastPageIndex; ++pageIndex ) + arrayAllocator()->releaseArrayPage( pages_[pageIndex] ); + size_ = newSize; + } + else if ( newSize > size_ ) + resolveReference( newSize ); +} + + +void +ValueInternalArray::makeIndexValid( ArrayIndex index ) +{ + // Need to enlarge page index ? + if ( index >= pageCount_ * itemsPerPage ) + { + PageIndex minNewPages = (index + 1) / itemsPerPage; + arrayAllocator()->reallocateArrayPageIndex( pages_, pageCount_, minNewPages ); + JSON_ASSERT_MESSAGE( pageCount_ >= minNewPages, "ValueInternalArray::reserve(): bad reallocation" ); + } + + // Need to allocate new pages ? + ArrayIndex nextPageIndex = + (size_ % itemsPerPage) != 0 ? size_ - (size_%itemsPerPage) + itemsPerPage + : size_; + if ( nextPageIndex <= index ) + { + PageIndex pageIndex = nextPageIndex / itemsPerPage; + PageIndex pageToAllocate = (index - nextPageIndex) / itemsPerPage + 1; + for ( ; pageToAllocate-- > 0; ++pageIndex ) + pages_[pageIndex] = arrayAllocator()->allocateArrayPage(); + } + + // Initialize all new entries + IteratorState it; + IteratorState itEnd; + makeIterator( it, size_ ); + size_ = index + 1; + makeIterator( itEnd, size_ ); + for ( ; !equals(it,itEnd); increment(it) ) + { + Value *value = &dereference(it); + new (value) Value(); // Construct a default value using placement new + } +} + +Value & +ValueInternalArray::resolveReference( ArrayIndex index ) +{ + if ( index >= size_ ) + makeIndexValid( index ); + return pages_[index/itemsPerPage][index%itemsPerPage]; +} + +Value * +ValueInternalArray::find( ArrayIndex index ) const +{ + if ( index >= size_ ) + return 0; + return &(pages_[index/itemsPerPage][index%itemsPerPage]); +} + +ValueInternalArray::ArrayIndex +ValueInternalArray::size() const +{ + return size_; +} + +int +ValueInternalArray::distance( const IteratorState &x, const IteratorState &y ) +{ + return indexOf(y) - indexOf(x); +} + + +ValueInternalArray::ArrayIndex +ValueInternalArray::indexOf( const IteratorState &iterator ) +{ + if ( !iterator.array_ ) + return ArrayIndex(-1); + return ArrayIndex( + (iterator.currentPageIndex_ - iterator.array_->pages_) * itemsPerPage + + iterator.currentItemIndex_ ); +} + + +int +ValueInternalArray::compare( const ValueInternalArray &other ) const +{ + int sizeDiff( size_ - other.size_ ); + if ( sizeDiff != 0 ) + return sizeDiff; + + for ( ArrayIndex index =0; index < size_; ++index ) + { + int diff = pages_[index/itemsPerPage][index%itemsPerPage].compare( + other.pages_[index/itemsPerPage][index%itemsPerPage] ); + if ( diff != 0 ) + return diff; + } + return 0; +} + +} // namespace Json diff --git a/deps/src/jsoncpp/json_internalmap.inl b/deps/src/jsoncpp/json_internalmap.inl new file mode 100644 index 000000000..f2fa16065 --- /dev/null +++ b/deps/src/jsoncpp/json_internalmap.inl @@ -0,0 +1,615 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +// included by json_value.cpp + +namespace Json { + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueInternalMap +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +/** \internal MUST be safely initialized using memset( this, 0, sizeof(ValueInternalLink) ); + * This optimization is used by the fast allocator. + */ +ValueInternalLink::ValueInternalLink() + : previous_( 0 ) + , next_( 0 ) +{ +} + +ValueInternalLink::~ValueInternalLink() +{ + for ( int index =0; index < itemPerLink; ++index ) + { + if ( !items_[index].isItemAvailable() ) + { + if ( !items_[index].isMemberNameStatic() ) + free( keys_[index] ); + } + else + break; + } +} + + + +ValueMapAllocator::~ValueMapAllocator() +{ +} + +#ifdef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR +class DefaultValueMapAllocator : public ValueMapAllocator +{ +public: // overridden from ValueMapAllocator + virtual ValueInternalMap *newMap() + { + return new ValueInternalMap(); + } + + virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) + { + return new ValueInternalMap( other ); + } + + virtual void destructMap( ValueInternalMap *map ) + { + delete map; + } + + virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) + { + return new ValueInternalLink[size]; + } + + virtual void releaseMapBuckets( ValueInternalLink *links ) + { + delete [] links; + } + + virtual ValueInternalLink *allocateMapLink() + { + return new ValueInternalLink(); + } + + virtual void releaseMapLink( ValueInternalLink *link ) + { + delete link; + } +}; +#else +/// @todo make this thread-safe (lock when accessign batch allocator) +class DefaultValueMapAllocator : public ValueMapAllocator +{ +public: // overridden from ValueMapAllocator + virtual ValueInternalMap *newMap() + { + ValueInternalMap *map = mapsAllocator_.allocate(); + new (map) ValueInternalMap(); // placement new + return map; + } + + virtual ValueInternalMap *newMapCopy( const ValueInternalMap &other ) + { + ValueInternalMap *map = mapsAllocator_.allocate(); + new (map) ValueInternalMap( other ); // placement new + return map; + } + + virtual void destructMap( ValueInternalMap *map ) + { + if ( map ) + { + map->~ValueInternalMap(); + mapsAllocator_.release( map ); + } + } + + virtual ValueInternalLink *allocateMapBuckets( unsigned int size ) + { + return new ValueInternalLink[size]; + } + + virtual void releaseMapBuckets( ValueInternalLink *links ) + { + delete [] links; + } + + virtual ValueInternalLink *allocateMapLink() + { + ValueInternalLink *link = linksAllocator_.allocate(); + memset( link, 0, sizeof(ValueInternalLink) ); + return link; + } + + virtual void releaseMapLink( ValueInternalLink *link ) + { + link->~ValueInternalLink(); + linksAllocator_.release( link ); + } +private: + BatchAllocator mapsAllocator_; + BatchAllocator linksAllocator_; +}; +#endif + +static ValueMapAllocator *&mapAllocator() +{ + static DefaultValueMapAllocator defaultAllocator; + static ValueMapAllocator *mapAllocator = &defaultAllocator; + return mapAllocator; +} + +static struct DummyMapAllocatorInitializer { + DummyMapAllocatorInitializer() + { + mapAllocator(); // ensure mapAllocator() statics are initialized before main(). + } +} dummyMapAllocatorInitializer; + + + +// h(K) = value * K >> w ; with w = 32 & K prime w.r.t. 2^32. + +/* +use linked list hash map. +buckets array is a container. +linked list element contains 6 key/values. (memory = (16+4) * 6 + 4 = 124) +value have extra state: valid, available, deleted +*/ + + +ValueInternalMap::ValueInternalMap() + : buckets_( 0 ) + , tailLink_( 0 ) + , bucketsSize_( 0 ) + , itemCount_( 0 ) +{ +} + + +ValueInternalMap::ValueInternalMap( const ValueInternalMap &other ) + : buckets_( 0 ) + , tailLink_( 0 ) + , bucketsSize_( 0 ) + , itemCount_( 0 ) +{ + reserve( other.itemCount_ ); + IteratorState it; + IteratorState itEnd; + other.makeBeginIterator( it ); + other.makeEndIterator( itEnd ); + for ( ; !equals(it,itEnd); increment(it) ) + { + bool isStatic; + const char *memberName = key( it, isStatic ); + const Value &aValue = value( it ); + resolveReference(memberName, isStatic) = aValue; + } +} + + +ValueInternalMap & +ValueInternalMap::operator =( const ValueInternalMap &other ) +{ + ValueInternalMap dummy( other ); + swap( dummy ); + return *this; +} + + +ValueInternalMap::~ValueInternalMap() +{ + if ( buckets_ ) + { + for ( BucketIndex bucketIndex =0; bucketIndex < bucketsSize_; ++bucketIndex ) + { + ValueInternalLink *link = buckets_[bucketIndex].next_; + while ( link ) + { + ValueInternalLink *linkToRelease = link; + link = link->next_; + mapAllocator()->releaseMapLink( linkToRelease ); + } + } + mapAllocator()->releaseMapBuckets( buckets_ ); + } +} + + +void +ValueInternalMap::swap( ValueInternalMap &other ) +{ + ValueInternalLink *tempBuckets = buckets_; + buckets_ = other.buckets_; + other.buckets_ = tempBuckets; + ValueInternalLink *tempTailLink = tailLink_; + tailLink_ = other.tailLink_; + other.tailLink_ = tempTailLink; + BucketIndex tempBucketsSize = bucketsSize_; + bucketsSize_ = other.bucketsSize_; + other.bucketsSize_ = tempBucketsSize; + BucketIndex tempItemCount = itemCount_; + itemCount_ = other.itemCount_; + other.itemCount_ = tempItemCount; +} + + +void +ValueInternalMap::clear() +{ + ValueInternalMap dummy; + swap( dummy ); +} + + +ValueInternalMap::BucketIndex +ValueInternalMap::size() const +{ + return itemCount_; +} + +bool +ValueInternalMap::reserveDelta( BucketIndex growth ) +{ + return reserve( itemCount_ + growth ); +} + +bool +ValueInternalMap::reserve( BucketIndex newItemCount ) +{ + if ( !buckets_ && newItemCount > 0 ) + { + buckets_ = mapAllocator()->allocateMapBuckets( 1 ); + bucketsSize_ = 1; + tailLink_ = &buckets_[0]; + } +// BucketIndex idealBucketCount = (newItemCount + ValueInternalLink::itemPerLink) / ValueInternalLink::itemPerLink; + return true; +} + + +const Value * +ValueInternalMap::find( const char *key ) const +{ + if ( !bucketsSize_ ) + return 0; + HashKey hashedKey = hash( key ); + BucketIndex bucketIndex = hashedKey % bucketsSize_; + for ( const ValueInternalLink *current = &buckets_[bucketIndex]; + current != 0; + current = current->next_ ) + { + for ( BucketIndex index=0; index < ValueInternalLink::itemPerLink; ++index ) + { + if ( current->items_[index].isItemAvailable() ) + return 0; + if ( strcmp( key, current->keys_[index] ) == 0 ) + return ¤t->items_[index]; + } + } + return 0; +} + + +Value * +ValueInternalMap::find( const char *key ) +{ + const ValueInternalMap *constThis = this; + return const_cast( constThis->find( key ) ); +} + + +Value & +ValueInternalMap::resolveReference( const char *key, + bool isStatic ) +{ + HashKey hashedKey = hash( key ); + if ( bucketsSize_ ) + { + BucketIndex bucketIndex = hashedKey % bucketsSize_; + ValueInternalLink **previous = 0; + BucketIndex index; + for ( ValueInternalLink *current = &buckets_[bucketIndex]; + current != 0; + previous = ¤t->next_, current = current->next_ ) + { + for ( index=0; index < ValueInternalLink::itemPerLink; ++index ) + { + if ( current->items_[index].isItemAvailable() ) + return setNewItem( key, isStatic, current, index ); + if ( strcmp( key, current->keys_[index] ) == 0 ) + return current->items_[index]; + } + } + } + + reserveDelta( 1 ); + return unsafeAdd( key, isStatic, hashedKey ); +} + + +void +ValueInternalMap::remove( const char *key ) +{ + HashKey hashedKey = hash( key ); + if ( !bucketsSize_ ) + return; + BucketIndex bucketIndex = hashedKey % bucketsSize_; + for ( ValueInternalLink *link = &buckets_[bucketIndex]; + link != 0; + link = link->next_ ) + { + BucketIndex index; + for ( index =0; index < ValueInternalLink::itemPerLink; ++index ) + { + if ( link->items_[index].isItemAvailable() ) + return; + if ( strcmp( key, link->keys_[index] ) == 0 ) + { + doActualRemove( link, index, bucketIndex ); + return; + } + } + } +} + +void +ValueInternalMap::doActualRemove( ValueInternalLink *link, + BucketIndex index, + BucketIndex bucketIndex ) +{ + // find last item of the bucket and swap it with the 'removed' one. + // set removed items flags to 'available'. + // if last page only contains 'available' items, then desallocate it (it's empty) + ValueInternalLink *&lastLink = getLastLinkInBucket( index ); + BucketIndex lastItemIndex = 1; // a link can never be empty, so start at 1 + for ( ; + lastItemIndex < ValueInternalLink::itemPerLink; + ++lastItemIndex ) // may be optimized with dicotomic search + { + if ( lastLink->items_[lastItemIndex].isItemAvailable() ) + break; + } + + BucketIndex lastUsedIndex = lastItemIndex - 1; + Value *valueToDelete = &link->items_[index]; + Value *valueToPreserve = &lastLink->items_[lastUsedIndex]; + if ( valueToDelete != valueToPreserve ) + valueToDelete->swap( *valueToPreserve ); + if ( lastUsedIndex == 0 ) // page is now empty + { // remove it from bucket linked list and delete it. + ValueInternalLink *linkPreviousToLast = lastLink->previous_; + if ( linkPreviousToLast != 0 ) // can not deleted bucket link. + { + mapAllocator()->releaseMapLink( lastLink ); + linkPreviousToLast->next_ = 0; + lastLink = linkPreviousToLast; + } + } + else + { + Value dummy; + valueToPreserve->swap( dummy ); // restore deleted to default Value. + valueToPreserve->setItemUsed( false ); + } + --itemCount_; +} + + +ValueInternalLink *& +ValueInternalMap::getLastLinkInBucket( BucketIndex bucketIndex ) +{ + if ( bucketIndex == bucketsSize_ - 1 ) + return tailLink_; + ValueInternalLink *&previous = buckets_[bucketIndex+1].previous_; + if ( !previous ) + previous = &buckets_[bucketIndex]; + return previous; +} + + +Value & +ValueInternalMap::setNewItem( const char *key, + bool isStatic, + ValueInternalLink *link, + BucketIndex index ) +{ + char *duplicatedKey = makeMemberName( key ); + ++itemCount_; + link->keys_[index] = duplicatedKey; + link->items_[index].setItemUsed(); + link->items_[index].setMemberNameIsStatic( isStatic ); + return link->items_[index]; // items already default constructed. +} + + +Value & +ValueInternalMap::unsafeAdd( const char *key, + bool isStatic, + HashKey hashedKey ) +{ + JSON_ASSERT_MESSAGE( bucketsSize_ > 0, "ValueInternalMap::unsafeAdd(): internal logic error." ); + BucketIndex bucketIndex = hashedKey % bucketsSize_; + ValueInternalLink *&previousLink = getLastLinkInBucket( bucketIndex ); + ValueInternalLink *link = previousLink; + BucketIndex index; + for ( index =0; index < ValueInternalLink::itemPerLink; ++index ) + { + if ( link->items_[index].isItemAvailable() ) + break; + } + if ( index == ValueInternalLink::itemPerLink ) // need to add a new page + { + ValueInternalLink *newLink = mapAllocator()->allocateMapLink(); + index = 0; + link->next_ = newLink; + previousLink = newLink; + link = newLink; + } + return setNewItem( key, isStatic, link, index ); +} + + +ValueInternalMap::HashKey +ValueInternalMap::hash( const char *key ) const +{ + HashKey hash = 0; + while ( *key ) + hash += *key++ * 37; + return hash; +} + + +int +ValueInternalMap::compare( const ValueInternalMap &other ) const +{ + int sizeDiff( itemCount_ - other.itemCount_ ); + if ( sizeDiff != 0 ) + return sizeDiff; + // Strict order guaranty is required. Compare all keys FIRST, then compare values. + IteratorState it; + IteratorState itEnd; + makeBeginIterator( it ); + makeEndIterator( itEnd ); + for ( ; !equals(it,itEnd); increment(it) ) + { + if ( !other.find( key( it ) ) ) + return 1; + } + + // All keys are equals, let's compare values + makeBeginIterator( it ); + for ( ; !equals(it,itEnd); increment(it) ) + { + const Value *otherValue = other.find( key( it ) ); + int valueDiff = value(it).compare( *otherValue ); + if ( valueDiff != 0 ) + return valueDiff; + } + return 0; +} + + +void +ValueInternalMap::makeBeginIterator( IteratorState &it ) const +{ + it.map_ = const_cast( this ); + it.bucketIndex_ = 0; + it.itemIndex_ = 0; + it.link_ = buckets_; +} + + +void +ValueInternalMap::makeEndIterator( IteratorState &it ) const +{ + it.map_ = const_cast( this ); + it.bucketIndex_ = bucketsSize_; + it.itemIndex_ = 0; + it.link_ = 0; +} + + +bool +ValueInternalMap::equals( const IteratorState &x, const IteratorState &other ) +{ + return x.map_ == other.map_ + && x.bucketIndex_ == other.bucketIndex_ + && x.link_ == other.link_ + && x.itemIndex_ == other.itemIndex_; +} + + +void +ValueInternalMap::incrementBucket( IteratorState &iterator ) +{ + ++iterator.bucketIndex_; + JSON_ASSERT_MESSAGE( iterator.bucketIndex_ <= iterator.map_->bucketsSize_, + "ValueInternalMap::increment(): attempting to iterate beyond end." ); + if ( iterator.bucketIndex_ == iterator.map_->bucketsSize_ ) + iterator.link_ = 0; + else + iterator.link_ = &(iterator.map_->buckets_[iterator.bucketIndex_]); + iterator.itemIndex_ = 0; +} + + +void +ValueInternalMap::increment( IteratorState &iterator ) +{ + JSON_ASSERT_MESSAGE( iterator.map_, "Attempting to iterator using invalid iterator." ); + ++iterator.itemIndex_; + if ( iterator.itemIndex_ == ValueInternalLink::itemPerLink ) + { + JSON_ASSERT_MESSAGE( iterator.link_ != 0, + "ValueInternalMap::increment(): attempting to iterate beyond end." ); + iterator.link_ = iterator.link_->next_; + if ( iterator.link_ == 0 ) + incrementBucket( iterator ); + } + else if ( iterator.link_->items_[iterator.itemIndex_].isItemAvailable() ) + { + incrementBucket( iterator ); + } +} + + +void +ValueInternalMap::decrement( IteratorState &iterator ) +{ + if ( iterator.itemIndex_ == 0 ) + { + JSON_ASSERT_MESSAGE( iterator.map_, "Attempting to iterate using invalid iterator." ); + if ( iterator.link_ == &iterator.map_->buckets_[iterator.bucketIndex_] ) + { + JSON_ASSERT_MESSAGE( iterator.bucketIndex_ > 0, "Attempting to iterate beyond beginning." ); + --(iterator.bucketIndex_); + } + iterator.link_ = iterator.link_->previous_; + iterator.itemIndex_ = ValueInternalLink::itemPerLink - 1; + } +} + + +const char * +ValueInternalMap::key( const IteratorState &iterator ) +{ + JSON_ASSERT_MESSAGE( iterator.link_, "Attempting to iterate using invalid iterator." ); + return iterator.link_->keys_[iterator.itemIndex_]; +} + +const char * +ValueInternalMap::key( const IteratorState &iterator, bool &isStatic ) +{ + JSON_ASSERT_MESSAGE( iterator.link_, "Attempting to iterate using invalid iterator." ); + isStatic = iterator.link_->items_[iterator.itemIndex_].isMemberNameStatic(); + return iterator.link_->keys_[iterator.itemIndex_]; +} + + +Value & +ValueInternalMap::value( const IteratorState &iterator ) +{ + JSON_ASSERT_MESSAGE( iterator.link_, "Attempting to iterate using invalid iterator." ); + return iterator.link_->items_[iterator.itemIndex_]; +} + + +int +ValueInternalMap::distance( const IteratorState &x, const IteratorState &y ) +{ + int offset = 0; + IteratorState it = x; + while ( !equals( it, y ) ) + increment( it ); + return offset; +} + +} // namespace Json diff --git a/deps/src/jsoncpp/json_reader.cpp b/deps/src/jsoncpp/json_reader.cpp new file mode 100644 index 000000000..0a06c21f5 --- /dev/null +++ b/deps/src/jsoncpp/json_reader.cpp @@ -0,0 +1,880 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +# include "json/reader.h" +# include "json/value.h" +# include "json_tool.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include + +#if _MSC_VER >= 1400 // VC++ 8.0 +#pragma warning( disable : 4996 ) // disable warning about strdup being deprecated. +#endif + +namespace Json { + +// Implementation of class Features +// //////////////////////////////// + +Features::Features() + : allowComments_( true ) + , strictRoot_( false ) +{ +} + + +Features +Features::all() +{ + return Features(); +} + + +Features +Features::strictMode() +{ + Features features; + features.allowComments_ = false; + features.strictRoot_ = true; + return features; +} + +// Implementation of class Reader +// //////////////////////////////// + + +static inline bool +in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4 ) +{ + return c == c1 || c == c2 || c == c3 || c == c4; +} + +static inline bool +in( Reader::Char c, Reader::Char c1, Reader::Char c2, Reader::Char c3, Reader::Char c4, Reader::Char c5 ) +{ + return c == c1 || c == c2 || c == c3 || c == c4 || c == c5; +} + + +static bool +containsNewLine( Reader::Location begin, + Reader::Location end ) +{ + for ( ;begin < end; ++begin ) + if ( *begin == '\n' || *begin == '\r' ) + return true; + return false; +} + + +// Class Reader +// ////////////////////////////////////////////////////////////////// + +Reader::Reader() + : features_( Features::all() ) +{ +} + + +Reader::Reader( const Features &features ) + : features_( features ) +{ +} + + +bool +Reader::parse( const std::string &document, + Value &root, + bool collectComments ) +{ + document_ = document; + const char *begin = document_.c_str(); + const char *end = begin + document_.length(); + return parse( begin, end, root, collectComments ); +} + + +bool +Reader::parse( std::istream& sin, + Value &root, + bool collectComments ) +{ + //std::istream_iterator begin(sin); + //std::istream_iterator end; + // Those would allow streamed input from a file, if parse() were a + // template function. + + // Since std::string is reference-counted, this at least does not + // create an extra copy. + std::string doc; + std::getline(sin, doc, (char)EOF); + return parse( doc, root, collectComments ); +} + +bool +Reader::parse( const char *beginDoc, const char *endDoc, + Value &root, + bool collectComments ) +{ + if ( !features_.allowComments_ ) + { + collectComments = false; + } + + begin_ = beginDoc; + end_ = endDoc; + collectComments_ = collectComments; + current_ = begin_; + lastValueEnd_ = 0; + lastValue_ = 0; + commentsBefore_ = ""; + errors_.clear(); + while ( !nodes_.empty() ) + nodes_.pop(); + nodes_.push( &root ); + + bool successful = readValue(); + Token token; + skipCommentTokens( token ); + if ( collectComments_ && !commentsBefore_.empty() ) + root.setComment( commentsBefore_, commentAfter ); + if ( features_.strictRoot_ ) + { + if ( !root.isArray() && !root.isObject() ) + { + // Set error location to start of doc, ideally should be first token found in doc + token.type_ = tokenError; + token.start_ = beginDoc; + token.end_ = endDoc; + addError( "A valid JSON document must be either an array or an object value.", + token ); + return false; + } + } + return successful; +} + + +bool +Reader::readValue() +{ + Token token; + skipCommentTokens( token ); + bool successful = true; + + if ( collectComments_ && !commentsBefore_.empty() ) + { + currentValue().setComment( commentsBefore_, commentBefore ); + commentsBefore_ = ""; + } + + + switch ( token.type_ ) + { + case tokenObjectBegin: + successful = readObject( token ); + break; + case tokenArrayBegin: + successful = readArray( token ); + break; + case tokenNumber: + successful = decodeNumber( token ); + break; + case tokenString: + successful = decodeString( token ); + break; + case tokenTrue: + currentValue() = true; + break; + case tokenFalse: + currentValue() = false; + break; + case tokenNull: + currentValue() = Value(); + break; + default: + return addError( "Syntax error: value, object or array expected.", token ); + } + + if ( collectComments_ ) + { + lastValueEnd_ = current_; + lastValue_ = ¤tValue(); + } + + return successful; +} + + +void +Reader::skipCommentTokens( Token &token ) +{ + if ( features_.allowComments_ ) + { + do + { + readToken( token ); + } + while ( token.type_ == tokenComment ); + } + else + { + readToken( token ); + } +} + + +bool +Reader::expectToken( TokenType type, Token &token, const char *message ) +{ + readToken( token ); + if ( token.type_ != type ) + return addError( message, token ); + return true; +} + + +bool +Reader::readToken( Token &token ) +{ + skipSpaces(); + token.start_ = current_; + Char c = getNextChar(); + bool ok = true; + switch ( c ) + { + case '{': + token.type_ = tokenObjectBegin; + break; + case '}': + token.type_ = tokenObjectEnd; + break; + case '[': + token.type_ = tokenArrayBegin; + break; + case ']': + token.type_ = tokenArrayEnd; + break; + case '"': + token.type_ = tokenString; + ok = readString(); + break; + case '/': + token.type_ = tokenComment; + ok = readComment(); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + token.type_ = tokenNumber; + readNumber(); + break; + case 't': + token.type_ = tokenTrue; + ok = match( "rue", 3 ); + break; + case 'f': + token.type_ = tokenFalse; + ok = match( "alse", 4 ); + break; + case 'n': + token.type_ = tokenNull; + ok = match( "ull", 3 ); + break; + case ',': + token.type_ = tokenArraySeparator; + break; + case ':': + token.type_ = tokenMemberSeparator; + break; + case 0: + token.type_ = tokenEndOfStream; + break; + default: + ok = false; + break; + } + if ( !ok ) + token.type_ = tokenError; + token.end_ = current_; + return true; +} + + +void +Reader::skipSpaces() +{ + while ( current_ != end_ ) + { + Char c = *current_; + if ( c == ' ' || c == '\t' || c == '\r' || c == '\n' ) + ++current_; + else + break; + } +} + + +bool +Reader::match( Location pattern, + int patternLength ) +{ + if ( end_ - current_ < patternLength ) + return false; + int index = patternLength; + while ( index-- ) + if ( current_[index] != pattern[index] ) + return false; + current_ += patternLength; + return true; +} + + +bool +Reader::readComment() +{ + Location commentBegin = current_ - 1; + Char c = getNextChar(); + bool successful = false; + if ( c == '*' ) + successful = readCStyleComment(); + else if ( c == '/' ) + successful = readCppStyleComment(); + if ( !successful ) + return false; + + if ( collectComments_ ) + { + CommentPlacement placement = commentBefore; + if ( lastValueEnd_ && !containsNewLine( lastValueEnd_, commentBegin ) ) + { + if ( c != '*' || !containsNewLine( commentBegin, current_ ) ) + placement = commentAfterOnSameLine; + } + + addComment( commentBegin, current_, placement ); + } + return true; +} + + +void +Reader::addComment( Location begin, + Location end, + CommentPlacement placement ) +{ + assert( collectComments_ ); + if ( placement == commentAfterOnSameLine ) + { + assert( lastValue_ != 0 ); + lastValue_->setComment( std::string( begin, end ), placement ); + } + else + { + if ( !commentsBefore_.empty() ) + commentsBefore_ += "\n"; + commentsBefore_ += std::string( begin, end ); + } +} + + +bool +Reader::readCStyleComment() +{ + while ( current_ != end_ ) + { + Char c = getNextChar(); + if ( c == '*' && *current_ == '/' ) + break; + } + return getNextChar() == '/'; +} + + +bool +Reader::readCppStyleComment() +{ + while ( current_ != end_ ) + { + Char c = getNextChar(); + if ( c == '\r' || c == '\n' ) + break; + } + return true; +} + + +void +Reader::readNumber() +{ + while ( current_ != end_ ) + { + if ( !(*current_ >= '0' && *current_ <= '9') && + !in( *current_, '.', 'e', 'E', '+', '-' ) ) + break; + ++current_; + } +} + +bool +Reader::readString() +{ + Char c = 0; + while ( current_ != end_ ) + { + c = getNextChar(); + if ( c == '\\' ) + getNextChar(); + else if ( c == '"' ) + break; + } + return c == '"'; +} + + +bool +Reader::readObject( Token &/*tokenStart*/ ) +{ + Token tokenName; + std::string name; + currentValue() = Value( objectValue ); + while ( readToken( tokenName ) ) + { + bool initialTokenOk = true; + while ( tokenName.type_ == tokenComment && initialTokenOk ) + initialTokenOk = readToken( tokenName ); + if ( !initialTokenOk ) + break; + if ( tokenName.type_ == tokenObjectEnd && name.empty() ) // empty object + return true; + if ( tokenName.type_ != tokenString ) + break; + + name = ""; + if ( !decodeString( tokenName, name ) ) + return recoverFromError( tokenObjectEnd ); + + Token colon; + if ( !readToken( colon ) || colon.type_ != tokenMemberSeparator ) + { + return addErrorAndRecover( "Missing ':' after object member name", + colon, + tokenObjectEnd ); + } + Value &value = currentValue()[ name ]; + nodes_.push( &value ); + bool ok = readValue(); + nodes_.pop(); + if ( !ok ) // error already set + return recoverFromError( tokenObjectEnd ); + + Token comma; + if ( !readToken( comma ) + || ( comma.type_ != tokenObjectEnd && + comma.type_ != tokenArraySeparator && + comma.type_ != tokenComment ) ) + { + return addErrorAndRecover( "Missing ',' or '}' in object declaration", + comma, + tokenObjectEnd ); + } + bool finalizeTokenOk = true; + while ( comma.type_ == tokenComment && + finalizeTokenOk ) + finalizeTokenOk = readToken( comma ); + if ( comma.type_ == tokenObjectEnd ) + return true; + } + return addErrorAndRecover( "Missing '}' or object member name", + tokenName, + tokenObjectEnd ); +} + + +bool +Reader::readArray( Token &/*tokenStart*/ ) +{ + currentValue() = Value( arrayValue ); + skipSpaces(); + if ( *current_ == ']' ) // empty array + { + Token endArray; + readToken( endArray ); + return true; + } + int index = 0; + for (;;) + { + Value &value = currentValue()[ index++ ]; + nodes_.push( &value ); + bool ok = readValue(); + nodes_.pop(); + if ( !ok ) // error already set + return recoverFromError( tokenArrayEnd ); + + Token token; + // Accept Comment after last item in the array. + ok = readToken( token ); + while ( token.type_ == tokenComment && ok ) + { + ok = readToken( token ); + } + bool badTokenType = ( token.type_ != tokenArraySeparator && + token.type_ != tokenArrayEnd ); + if ( !ok || badTokenType ) + { + return addErrorAndRecover( "Missing ',' or ']' in array declaration", + token, + tokenArrayEnd ); + } + if ( token.type_ == tokenArrayEnd ) + break; + } + return true; +} + + +bool +Reader::decodeNumber( Token &token ) +{ + bool isDouble = false; + for ( Location inspect = token.start_; inspect != token.end_; ++inspect ) + { + isDouble = isDouble + || in( *inspect, '.', 'e', 'E', '+' ) + || ( *inspect == '-' && inspect != token.start_ ); + } + if ( isDouble ) + return decodeDouble( token ); + // Attempts to parse the number as an integer. If the number is + // larger than the maximum supported value of an integer then + // we decode the number as a double. + Location current = token.start_; + bool isNegative = *current == '-'; + if ( isNegative ) + ++current; + Value::LargestUInt maxIntegerValue = isNegative ? Value::LargestUInt(-Value::minLargestInt) + : Value::maxLargestUInt; + Value::LargestUInt threshold = maxIntegerValue / 10; + Value::UInt lastDigitThreshold = Value::UInt( maxIntegerValue % 10 ); + assert( lastDigitThreshold >=0 && lastDigitThreshold <= 9 ); + Value::LargestUInt value = 0; + while ( current < token.end_ ) + { + Char c = *current++; + if ( c < '0' || c > '9' ) + return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token ); + Value::UInt digit(c - '0'); + if ( value >= threshold ) + { + // If the current digit is not the last one, or if it is + // greater than the last digit of the maximum integer value, + // the parse the number as a double. + if ( current != token.end_ || digit > lastDigitThreshold ) + { + return decodeDouble( token ); + } + } + value = value * 10 + digit; + } + if ( isNegative ) + currentValue() = -Value::LargestInt( value ); + else if ( value <= Value::LargestUInt(Value::maxInt) ) + currentValue() = Value::LargestInt( value ); + else + currentValue() = value; + return true; +} + + +bool +Reader::decodeDouble( Token &token ) +{ + double value = 0; + const int bufferSize = 32; + int count; + int length = int(token.end_ - token.start_); + if ( length <= bufferSize ) + { + Char buffer[bufferSize+1]; + memcpy( buffer, token.start_, length ); + buffer[length] = 0; + count = sscanf( buffer, "%lf", &value ); + } + else + { + std::string buffer( token.start_, token.end_ ); + count = sscanf( buffer.c_str(), "%lf", &value ); + } + + if ( count != 1 ) + return addError( "'" + std::string( token.start_, token.end_ ) + "' is not a number.", token ); + currentValue() = value; + return true; +} + + +bool +Reader::decodeString( Token &token ) +{ + std::string decoded; + if ( !decodeString( token, decoded ) ) + return false; + currentValue() = decoded; + return true; +} + + +bool +Reader::decodeString( Token &token, std::string &decoded ) +{ + decoded.reserve( token.end_ - token.start_ - 2 ); + Location current = token.start_ + 1; // skip '"' + Location end = token.end_ - 1; // do not include '"' + while ( current != end ) + { + Char c = *current++; + if ( c == '"' ) + break; + else if ( c == '\\' ) + { + if ( current == end ) + return addError( "Empty escape sequence in string", token, current ); + Char escape = *current++; + switch ( escape ) + { + case '"': decoded += '"'; break; + case '/': decoded += '/'; break; + case '\\': decoded += '\\'; break; + case 'b': decoded += '\b'; break; + case 'f': decoded += '\f'; break; + case 'n': decoded += '\n'; break; + case 'r': decoded += '\r'; break; + case 't': decoded += '\t'; break; + case 'u': + { + unsigned int unicode; + if ( !decodeUnicodeCodePoint( token, current, end, unicode ) ) + return false; + decoded += codePointToUTF8(unicode); + } + break; + default: + return addError( "Bad escape sequence in string", token, current ); + } + } + else + { + decoded += c; + } + } + return true; +} + +bool +Reader::decodeUnicodeCodePoint( Token &token, + Location ¤t, + Location end, + unsigned int &unicode ) +{ + + if ( !decodeUnicodeEscapeSequence( token, current, end, unicode ) ) + return false; + if (unicode >= 0xD800 && unicode <= 0xDBFF) + { + // surrogate pairs + if (end - current < 6) + return addError( "additional six characters expected to parse unicode surrogate pair.", token, current ); + unsigned int surrogatePair; + if (*(current++) == '\\' && *(current++)== 'u') + { + if (decodeUnicodeEscapeSequence( token, current, end, surrogatePair )) + { + unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF); + } + else + return false; + } + else + return addError( "expecting another \\u token to begin the second half of a unicode surrogate pair", token, current ); + } + return true; +} + +bool +Reader::decodeUnicodeEscapeSequence( Token &token, + Location ¤t, + Location end, + unsigned int &unicode ) +{ + if ( end - current < 4 ) + return addError( "Bad unicode escape sequence in string: four digits expected.", token, current ); + unicode = 0; + for ( int index =0; index < 4; ++index ) + { + Char c = *current++; + unicode *= 16; + if ( c >= '0' && c <= '9' ) + unicode += c - '0'; + else if ( c >= 'a' && c <= 'f' ) + unicode += c - 'a' + 10; + else if ( c >= 'A' && c <= 'F' ) + unicode += c - 'A' + 10; + else + return addError( "Bad unicode escape sequence in string: hexadecimal digit expected.", token, current ); + } + return true; +} + + +bool +Reader::addError( const std::string &message, + Token &token, + Location extra ) +{ + ErrorInfo info; + info.token_ = token; + info.message_ = message; + info.extra_ = extra; + errors_.push_back( info ); + return false; +} + + +bool +Reader::recoverFromError( TokenType skipUntilToken ) +{ + int errorCount = int(errors_.size()); + Token skip; + for (;;) + { + if ( !readToken(skip) ) + errors_.resize( errorCount ); // discard errors caused by recovery + if ( skip.type_ == skipUntilToken || skip.type_ == tokenEndOfStream ) + break; + } + errors_.resize( errorCount ); + return false; +} + + +bool +Reader::addErrorAndRecover( const std::string &message, + Token &token, + TokenType skipUntilToken ) +{ + addError( message, token ); + return recoverFromError( skipUntilToken ); +} + + +Value & +Reader::currentValue() +{ + return *(nodes_.top()); +} + + +Reader::Char +Reader::getNextChar() +{ + if ( current_ == end_ ) + return 0; + return *current_++; +} + + +void +Reader::getLocationLineAndColumn( Location location, + int &line, + int &column ) const +{ + Location current = begin_; + Location lastLineStart = current; + line = 0; + while ( current < location && current != end_ ) + { + Char c = *current++; + if ( c == '\r' ) + { + if ( *current == '\n' ) + ++current; + lastLineStart = current; + ++line; + } + else if ( c == '\n' ) + { + lastLineStart = current; + ++line; + } + } + // column & line start at 1 + column = int(location - lastLineStart) + 1; + ++line; +} + + +std::string +Reader::getLocationLineAndColumn( Location location ) const +{ + int line, column; + getLocationLineAndColumn( location, line, column ); + char buffer[18+16+16+1]; + sprintf( buffer, "Line %d, Column %d", line, column ); + return buffer; +} + + +// Deprecated. Preserved for backward compatibility +std::string +Reader::getFormatedErrorMessages() const +{ + return getFormattedErrorMessages(); +} + + +std::string +Reader::getFormattedErrorMessages() const +{ + std::string formattedMessage; + for ( Errors::const_iterator itError = errors_.begin(); + itError != errors_.end(); + ++itError ) + { + const ErrorInfo &error = *itError; + formattedMessage += "* " + getLocationLineAndColumn( error.token_.start_ ) + "\n"; + formattedMessage += " " + error.message_ + "\n"; + if ( error.extra_ ) + formattedMessage += "See " + getLocationLineAndColumn( error.extra_ ) + " for detail.\n"; + } + return formattedMessage; +} + + +std::istream& operator>>( std::istream &sin, Value &root ) +{ + Json::Reader reader; + bool ok = reader.parse(sin, root, true); + //JSON_ASSERT( ok ); + if (!ok) throw std::runtime_error(reader.getFormattedErrorMessages()); + return sin; +} + + +} // namespace Json diff --git a/deps/src/jsoncpp/json_tool.h b/deps/src/jsoncpp/json_tool.h new file mode 100644 index 000000000..658031bbb --- /dev/null +++ b/deps/src/jsoncpp/json_tool.h @@ -0,0 +1,93 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef LIB_JSONCPP_JSON_TOOL_H_INCLUDED +# define LIB_JSONCPP_JSON_TOOL_H_INCLUDED + +/* This header provides common string manipulation support, such as UTF-8, + * portable conversion from/to string... + * + * It is an internal header that must not be exposed. + */ + +namespace Json { + +/// Converts a unicode code-point to UTF-8. +static inline std::string +codePointToUTF8(unsigned int cp) +{ + std::string result; + + // based on description from http://en.wikipedia.org/wiki/UTF-8 + + if (cp <= 0x7f) + { + result.resize(1); + result[0] = static_cast(cp); + } + else if (cp <= 0x7FF) + { + result.resize(2); + result[1] = static_cast(0x80 | (0x3f & cp)); + result[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); + } + else if (cp <= 0xFFFF) + { + result.resize(3); + result[2] = static_cast(0x80 | (0x3f & cp)); + result[1] = 0x80 | static_cast((0x3f & (cp >> 6))); + result[0] = 0xE0 | static_cast((0xf & (cp >> 12))); + } + else if (cp <= 0x10FFFF) + { + result.resize(4); + result[3] = static_cast(0x80 | (0x3f & cp)); + result[2] = static_cast(0x80 | (0x3f & (cp >> 6))); + result[1] = static_cast(0x80 | (0x3f & (cp >> 12))); + result[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); + } + + return result; +} + + +/// Returns true if ch is a control character (in range [0,32[). +static inline bool +isControlCharacter(char ch) +{ + return ch > 0 && ch <= 0x1F; +} + + +enum { + /// Constant that specify the size of the buffer that must be passed to uintToString. + uintToStringBufferSize = 3*sizeof(LargestUInt)+1 +}; + +// Defines a char buffer for use with uintToString(). +typedef char UIntToStringBuffer[uintToStringBufferSize]; + + +/** Converts an unsigned integer to string. + * @param value Unsigned interger to convert to string + * @param current Input/Output string buffer. + * Must have at least uintToStringBufferSize chars free. + */ +static inline void +uintToString( LargestUInt value, + char *¤t ) +{ + *--current = 0; + do + { + *--current = char(value % 10) + '0'; + value /= 10; + } + while ( value != 0 ); +} + +} // namespace Json { + +#endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED diff --git a/deps/src/jsoncpp/json_value.cpp b/deps/src/jsoncpp/json_value.cpp new file mode 100644 index 000000000..96aa56ee7 --- /dev/null +++ b/deps/src/jsoncpp/json_value.cpp @@ -0,0 +1,1829 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +# include "json/value.h" +# include "json/writer.h" +# ifndef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR +# include "json_batchallocator.h" +# endif // #ifndef JSON_USE_SIMPLE_INTERNAL_ALLOCATOR +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#ifdef JSON_USE_CPPTL +# include +#endif +#include // size_t + +#define JSON_ASSERT_UNREACHABLE assert( false ) +#define JSON_ASSERT( condition ) assert( condition ); // @todo <= change this into an exception throw +#define JSON_FAIL_MESSAGE( message ) throw std::runtime_error( message ); +#define JSON_ASSERT_MESSAGE( condition, message ) if (!( condition )) JSON_FAIL_MESSAGE( message ) + +namespace Json { + +const Value Value::null; +const Int Value::minInt = Int( ~(UInt(-1)/2) ); +const Int Value::maxInt = Int( UInt(-1)/2 ); +const UInt Value::maxUInt = UInt(-1); +const Int64 Value::minInt64 = Int64( ~(UInt64(-1)/2) ); +const Int64 Value::maxInt64 = Int64( UInt64(-1)/2 ); +const UInt64 Value::maxUInt64 = UInt64(-1); +const LargestInt Value::minLargestInt = LargestInt( ~(LargestUInt(-1)/2) ); +const LargestInt Value::maxLargestInt = LargestInt( LargestUInt(-1)/2 ); +const LargestUInt Value::maxLargestUInt = LargestUInt(-1); + + +/// Unknown size marker +static const unsigned int unknown = (unsigned)-1; + + +/** Duplicates the specified string value. + * @param value Pointer to the string to duplicate. Must be zero-terminated if + * length is "unknown". + * @param length Length of the value. if equals to unknown, then it will be + * computed using strlen(value). + * @return Pointer on the duplicate instance of string. + */ +static inline char * +duplicateStringValue( const char *value, + unsigned int length = unknown ) +{ + if ( length == unknown ) + length = (unsigned int)strlen(value); + char *newString = static_cast( malloc( length + 1 ) ); + JSON_ASSERT_MESSAGE( newString != 0, "Failed to allocate string value buffer" ); + memcpy( newString, value, length ); + newString[length] = 0; + return newString; +} + + +/** Free the string duplicated by duplicateStringValue(). + */ +static inline void +releaseStringValue( char *value ) +{ + if ( value ) + free( value ); +} + +} // namespace Json + + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ValueInternals... +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +#if !defined(JSON_IS_AMALGAMATION) +# ifdef JSON_VALUE_USE_INTERNAL_MAP +# include "json_internalarray.inl" +# include "json_internalmap.inl" +# endif // JSON_VALUE_USE_INTERNAL_MAP + +# include "json_valueiterator.inl" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::CommentInfo +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + + +Value::CommentInfo::CommentInfo() + : comment_( 0 ) +{ +} + +Value::CommentInfo::~CommentInfo() +{ + if ( comment_ ) + releaseStringValue( comment_ ); +} + + +void +Value::CommentInfo::setComment( const char *text ) +{ + if ( comment_ ) + releaseStringValue( comment_ ); + JSON_ASSERT( text != 0 ); + JSON_ASSERT_MESSAGE( text[0]=='\0' || text[0]=='/', "Comments must start with /"); + // It seems that /**/ style comments are acceptable as well. + comment_ = duplicateStringValue( text ); +} + + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::CZString +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +# ifndef JSON_VALUE_USE_INTERNAL_MAP + +// Notes: index_ indicates if the string was allocated when +// a string is stored. + +Value::CZString::CZString( ArrayIndex index ) + : cstr_( 0 ) + , index_( index ) +{ +} + +Value::CZString::CZString( const char *cstr, DuplicationPolicy allocate ) + : cstr_( allocate == duplicate ? duplicateStringValue(cstr) + : cstr ) + , index_( allocate ) +{ +} + +Value::CZString::CZString( const CZString &other ) +: cstr_( other.index_ != noDuplication && other.cstr_ != 0 + ? duplicateStringValue( other.cstr_ ) + : other.cstr_ ) + , index_( other.cstr_ ? (other.index_ == noDuplication ? noDuplication : duplicate) + : other.index_ ) +{ +} + +Value::CZString::~CZString() +{ + if ( cstr_ && index_ == duplicate ) + releaseStringValue( const_cast( cstr_ ) ); +} + +void +Value::CZString::swap( CZString &other ) +{ + std::swap( cstr_, other.cstr_ ); + std::swap( index_, other.index_ ); +} + +Value::CZString & +Value::CZString::operator =( const CZString &other ) +{ + CZString temp( other ); + swap( temp ); + return *this; +} + +bool +Value::CZString::operator<( const CZString &other ) const +{ + if ( cstr_ ) + return strcmp( cstr_, other.cstr_ ) < 0; + return index_ < other.index_; +} + +bool +Value::CZString::operator==( const CZString &other ) const +{ + if ( cstr_ ) + return strcmp( cstr_, other.cstr_ ) == 0; + return index_ == other.index_; +} + + +ArrayIndex +Value::CZString::index() const +{ + return index_; +} + + +const char * +Value::CZString::c_str() const +{ + return cstr_; +} + +bool +Value::CZString::isStaticString() const +{ + return index_ == noDuplication; +} + +#endif // ifndef JSON_VALUE_USE_INTERNAL_MAP + + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class Value::Value +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +/*! \internal Default constructor initialization must be equivalent to: + * memset( this, 0, sizeof(Value) ) + * This optimization is used in ValueInternalMap fast allocator. + */ +Value::Value( ValueType type ) + : type_( type ) + , allocated_( 0 ) + , comments_( 0 ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif +{ + switch ( type ) + { + case nullValue: + break; + case intValue: + case uintValue: + value_.int_ = 0; + break; + case realValue: + value_.real_ = 0.0; + break; + case stringValue: + value_.string_ = 0; + break; +#ifndef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues(); + break; +#else + case arrayValue: + value_.array_ = arrayAllocator()->newArray(); + break; + case objectValue: + value_.map_ = mapAllocator()->newMap(); + break; +#endif + case booleanValue: + value_.bool_ = false; + break; + default: + JSON_ASSERT_UNREACHABLE; + } +} + + +#if defined(JSON_HAS_INT64) +Value::Value( UInt value ) + : type_( uintValue ) + , comments_( 0 ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif +{ + value_.uint_ = value; +} + +Value::Value( Int value ) + : type_( intValue ) + , comments_( 0 ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif +{ + value_.int_ = value; +} + +#endif // if defined(JSON_HAS_INT64) + + +Value::Value( Int64 value ) + : type_( intValue ) + , comments_( 0 ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif +{ + value_.int_ = value; +} + + +Value::Value( UInt64 value ) + : type_( uintValue ) + , comments_( 0 ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif +{ + value_.uint_ = value; +} + +Value::Value( double value ) + : type_( realValue ) + , comments_( 0 ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif +{ + value_.real_ = value; +} + +Value::Value( const char *value ) + : type_( stringValue ) + , allocated_( true ) + , comments_( 0 ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif +{ + value_.string_ = duplicateStringValue( value ); +} + + +Value::Value( const char *beginValue, + const char *endValue ) + : type_( stringValue ) + , allocated_( true ) + , comments_( 0 ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif +{ + value_.string_ = duplicateStringValue( beginValue, + (unsigned int)(endValue - beginValue) ); +} + + +Value::Value( const std::string &value ) + : type_( stringValue ) + , allocated_( true ) + , comments_( 0 ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif +{ + value_.string_ = duplicateStringValue( value.c_str(), + (unsigned int)value.length() ); + +} + +Value::Value( const StaticString &value ) + : type_( stringValue ) + , allocated_( false ) + , comments_( 0 ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif +{ + value_.string_ = const_cast( value.c_str() ); +} + + +# ifdef JSON_USE_CPPTL +Value::Value( const CppTL::ConstString &value ) + : type_( stringValue ) + , allocated_( true ) + , comments_( 0 ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif +{ + value_.string_ = duplicateStringValue( value, value.length() ); +} +# endif + +Value::Value( bool value ) + : type_( booleanValue ) + , comments_( 0 ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif +{ + value_.bool_ = value; +} + + +Value::Value( const Value &other ) + : type_( other.type_ ) + , comments_( 0 ) +# ifdef JSON_VALUE_USE_INTERNAL_MAP + , itemIsUsed_( 0 ) +#endif +{ + switch ( type_ ) + { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + value_ = other.value_; + break; + case stringValue: + if ( other.value_.string_ ) + { + value_.string_ = duplicateStringValue( other.value_.string_ ); + allocated_ = true; + } + else + value_.string_ = 0; + break; +#ifndef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + case objectValue: + value_.map_ = new ObjectValues( *other.value_.map_ ); + break; +#else + case arrayValue: + value_.array_ = arrayAllocator()->newArrayCopy( *other.value_.array_ ); + break; + case objectValue: + value_.map_ = mapAllocator()->newMapCopy( *other.value_.map_ ); + break; +#endif + default: + JSON_ASSERT_UNREACHABLE; + } + if ( other.comments_ ) + { + comments_ = new CommentInfo[numberOfCommentPlacement]; + for ( int comment =0; comment < numberOfCommentPlacement; ++comment ) + { + const CommentInfo &otherComment = other.comments_[comment]; + if ( otherComment.comment_ ) + comments_[comment].setComment( otherComment.comment_ ); + } + } +} + + +Value::~Value() +{ + switch ( type_ ) + { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + break; + case stringValue: + if ( allocated_ ) + releaseStringValue( value_.string_ ); + break; +#ifndef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + case objectValue: + delete value_.map_; + break; +#else + case arrayValue: + arrayAllocator()->destructArray( value_.array_ ); + break; + case objectValue: + mapAllocator()->destructMap( value_.map_ ); + break; +#endif + default: + JSON_ASSERT_UNREACHABLE; + } + + if ( comments_ ) + delete[] comments_; +} + +Value & +Value::operator=( const Value &other ) +{ + Value temp( other ); + swap( temp ); + return *this; +} + +void +Value::swap( Value &other ) +{ + ValueType temp = type_; + type_ = other.type_; + other.type_ = temp; + std::swap( value_, other.value_ ); + int temp2 = allocated_; + allocated_ = other.allocated_; + other.allocated_ = temp2; +} + +ValueType +Value::type() const +{ + return type_; +} + + +int +Value::compare( const Value &other ) const +{ + if ( *this < other ) + return -1; + if ( *this > other ) + return 1; + return 0; +} + + +bool +Value::operator <( const Value &other ) const +{ + int typeDelta = type_ - other.type_; + if ( typeDelta ) + return typeDelta < 0 ? true : false; + switch ( type_ ) + { + case nullValue: + return false; + case intValue: + return value_.int_ < other.value_.int_; + case uintValue: + return value_.uint_ < other.value_.uint_; + case realValue: + return value_.real_ < other.value_.real_; + case booleanValue: + return value_.bool_ < other.value_.bool_; + case stringValue: + return ( value_.string_ == 0 && other.value_.string_ ) + || ( other.value_.string_ + && value_.string_ + && strcmp( value_.string_, other.value_.string_ ) < 0 ); +#ifndef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + case objectValue: + { + int delta = int( value_.map_->size() - other.value_.map_->size() ); + if ( delta ) + return delta < 0; + return (*value_.map_) < (*other.value_.map_); + } +#else + case arrayValue: + return value_.array_->compare( *(other.value_.array_) ) < 0; + case objectValue: + return value_.map_->compare( *(other.value_.map_) ) < 0; +#endif + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable +} + +bool +Value::operator <=( const Value &other ) const +{ + return !(other < *this); +} + +bool +Value::operator >=( const Value &other ) const +{ + return !(*this < other); +} + +bool +Value::operator >( const Value &other ) const +{ + return other < *this; +} + +bool +Value::operator ==( const Value &other ) const +{ + //if ( type_ != other.type_ ) + // GCC 2.95.3 says: + // attempt to take address of bit-field structure member `Json::Value::type_' + // Beats me, but a temp solves the problem. + int temp = other.type_; + if ( type_ != temp ) + return false; + switch ( type_ ) + { + case nullValue: + return true; + case intValue: + return value_.int_ == other.value_.int_; + case uintValue: + return value_.uint_ == other.value_.uint_; + case realValue: + return value_.real_ == other.value_.real_; + case booleanValue: + return value_.bool_ == other.value_.bool_; + case stringValue: + return ( value_.string_ == other.value_.string_ ) + || ( other.value_.string_ + && value_.string_ + && strcmp( value_.string_, other.value_.string_ ) == 0 ); +#ifndef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + case objectValue: + return value_.map_->size() == other.value_.map_->size() + && (*value_.map_) == (*other.value_.map_); +#else + case arrayValue: + return value_.array_->compare( *(other.value_.array_) ) == 0; + case objectValue: + return value_.map_->compare( *(other.value_.map_) ) == 0; +#endif + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable +} + +bool +Value::operator !=( const Value &other ) const +{ + return !( *this == other ); +} + +const char * +Value::asCString() const +{ + JSON_ASSERT( type_ == stringValue ); + return value_.string_; +} + + +std::string +Value::asString() const +{ + switch ( type_ ) + { + case nullValue: + return ""; + case stringValue: + return value_.string_ ? value_.string_ : ""; + case booleanValue: + return value_.bool_ ? "true" : "false"; + case intValue: + case uintValue: + case realValue: + case arrayValue: + case objectValue: + JSON_FAIL_MESSAGE( "Type is not convertible to string" ); + default: + JSON_ASSERT_UNREACHABLE; + } + return ""; // unreachable +} + +# ifdef JSON_USE_CPPTL +CppTL::ConstString +Value::asConstString() const +{ + return CppTL::ConstString( asString().c_str() ); +} +# endif + + +Value::Int +Value::asInt() const +{ + switch ( type_ ) + { + case nullValue: + return 0; + case intValue: + JSON_ASSERT_MESSAGE( value_.int_ >= minInt && value_.int_ <= maxInt, "unsigned integer out of signed int range" ); + return Int(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE( value_.uint_ <= UInt(maxInt), "unsigned integer out of signed int range" ); + return Int(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE( value_.real_ >= minInt && value_.real_ <= maxInt, "Real out of signed integer range" ); + return Int( value_.real_ ); + case booleanValue: + return value_.bool_ ? 1 : 0; + case stringValue: + case arrayValue: + case objectValue: + JSON_FAIL_MESSAGE( "Type is not convertible to int" ); + default: + JSON_ASSERT_UNREACHABLE; + } + return 0; // unreachable; +} + + +Value::UInt +Value::asUInt() const +{ + switch ( type_ ) + { + case nullValue: + return 0; + case intValue: + JSON_ASSERT_MESSAGE( value_.int_ >= 0, "Negative integer can not be converted to unsigned integer" ); + JSON_ASSERT_MESSAGE( value_.int_ <= maxUInt, "signed integer out of UInt range" ); + return UInt(value_.int_); + case uintValue: + JSON_ASSERT_MESSAGE( value_.uint_ <= maxUInt, "unsigned integer out of UInt range" ); + return UInt(value_.uint_); + case realValue: + JSON_ASSERT_MESSAGE( value_.real_ >= 0 && value_.real_ <= maxUInt, "Real out of unsigned integer range" ); + return UInt( value_.real_ ); + case booleanValue: + return value_.bool_ ? 1 : 0; + case stringValue: + case arrayValue: + case objectValue: + JSON_FAIL_MESSAGE( "Type is not convertible to uint" ); + default: + JSON_ASSERT_UNREACHABLE; + } + return 0; // unreachable; +} + + +# if defined(JSON_HAS_INT64) + +Value::Int64 +Value::asInt64() const +{ + switch ( type_ ) + { + case nullValue: + return 0; + case intValue: + return value_.int_; + case uintValue: + JSON_ASSERT_MESSAGE( value_.uint_ <= UInt64(maxInt64), "unsigned integer out of Int64 range" ); + return value_.uint_; + case realValue: + JSON_ASSERT_MESSAGE( value_.real_ >= minInt64 && value_.real_ <= maxInt64, "Real out of Int64 range" ); + return Int( value_.real_ ); + case booleanValue: + return value_.bool_ ? 1 : 0; + case stringValue: + case arrayValue: + case objectValue: + JSON_FAIL_MESSAGE( "Type is not convertible to Int64" ); + default: + JSON_ASSERT_UNREACHABLE; + } + return 0; // unreachable; +} + + +Value::UInt64 +Value::asUInt64() const +{ + switch ( type_ ) + { + case nullValue: + return 0; + case intValue: + JSON_ASSERT_MESSAGE( value_.int_ >= 0, "Negative integer can not be converted to UInt64" ); + return value_.int_; + case uintValue: + return value_.uint_; + case realValue: + JSON_ASSERT_MESSAGE( value_.real_ >= 0 && value_.real_ <= maxUInt64, "Real out of UInt64 range" ); + return UInt( value_.real_ ); + case booleanValue: + return value_.bool_ ? 1 : 0; + case stringValue: + case arrayValue: + case objectValue: + JSON_FAIL_MESSAGE( "Type is not convertible to UInt64" ); + default: + JSON_ASSERT_UNREACHABLE; + } + return 0; // unreachable; +} +# endif // if defined(JSON_HAS_INT64) + + +LargestInt +Value::asLargestInt() const +{ +#if defined(JSON_NO_INT64) + return asInt(); +#else + return asInt64(); +#endif +} + + +LargestUInt +Value::asLargestUInt() const +{ +#if defined(JSON_NO_INT64) + return asUInt(); +#else + return asUInt64(); +#endif +} + + +double +Value::asDouble() const +{ + switch ( type_ ) + { + case nullValue: + return 0.0; + case intValue: + return static_cast( value_.int_ ); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast( value_.uint_ ); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast( Int(value_.uint_/2) ) * 2 + Int(value_.uint_ & 1); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return value_.real_; + case booleanValue: + return value_.bool_ ? 1.0 : 0.0; + case stringValue: + case arrayValue: + case objectValue: + JSON_FAIL_MESSAGE( "Type is not convertible to double" ); + default: + JSON_ASSERT_UNREACHABLE; + } + return 0; // unreachable; +} + +float +Value::asFloat() const +{ + switch ( type_ ) + { + case nullValue: + return 0.0f; + case intValue: + return static_cast( value_.int_ ); + case uintValue: +#if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast( value_.uint_ ); +#else // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + return static_cast( Int(value_.uint_/2) ) * 2 + Int(value_.uint_ & 1); +#endif // if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) + case realValue: + return static_cast( value_.real_ ); + case booleanValue: + return value_.bool_ ? 1.0f : 0.0f; + case stringValue: + case arrayValue: + case objectValue: + JSON_FAIL_MESSAGE( "Type is not convertible to float" ); + default: + JSON_ASSERT_UNREACHABLE; + } + return 0.0f; // unreachable; +} + +bool +Value::asBool() const +{ + switch ( type_ ) + { + case nullValue: + return false; + case intValue: + case uintValue: + return value_.int_ != 0; + case realValue: + return value_.real_ != 0.0; + case booleanValue: + return value_.bool_; + case stringValue: + return value_.string_ && value_.string_[0] != 0; + case arrayValue: + case objectValue: + return value_.map_->size() != 0; + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable; +} + + +bool +Value::isConvertibleTo( ValueType other ) const +{ + switch ( type_ ) + { + case nullValue: + return true; + case intValue: + return ( other == nullValue && value_.int_ == 0 ) + || other == intValue + || ( other == uintValue && value_.int_ >= 0 ) + || other == realValue + || other == stringValue + || other == booleanValue; + case uintValue: + return ( other == nullValue && value_.uint_ == 0 ) + || ( other == intValue && value_.uint_ <= (unsigned)maxInt ) + || other == uintValue + || other == realValue + || other == stringValue + || other == booleanValue; + case realValue: + return ( other == nullValue && value_.real_ == 0.0 ) + || ( other == intValue && value_.real_ >= minInt && value_.real_ <= maxInt ) + || ( other == uintValue && value_.real_ >= 0 && value_.real_ <= maxUInt ) + || other == realValue + || other == stringValue + || other == booleanValue; + case booleanValue: + return ( other == nullValue && value_.bool_ == false ) + || other == intValue + || other == uintValue + || other == realValue + || other == stringValue + || other == booleanValue; + case stringValue: + return other == stringValue + || ( other == nullValue && (!value_.string_ || value_.string_[0] == 0) ); + case arrayValue: + return other == arrayValue + || ( other == nullValue && value_.map_->size() == 0 ); + case objectValue: + return other == objectValue + || ( other == nullValue && value_.map_->size() == 0 ); + default: + JSON_ASSERT_UNREACHABLE; + } + return false; // unreachable; +} + + +/// Number of values in array or object +ArrayIndex +Value::size() const +{ + switch ( type_ ) + { + case nullValue: + case intValue: + case uintValue: + case realValue: + case booleanValue: + case stringValue: + return 0; +#ifndef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: // size of the array is highest index + 1 + if ( !value_.map_->empty() ) + { + ObjectValues::const_iterator itLast = value_.map_->end(); + --itLast; + return (*itLast).first.index()+1; + } + return 0; + case objectValue: + return ArrayIndex( value_.map_->size() ); +#else + case arrayValue: + return Int( value_.array_->size() ); + case objectValue: + return Int( value_.map_->size() ); +#endif + default: + JSON_ASSERT_UNREACHABLE; + } + return 0; // unreachable; +} + + +bool +Value::empty() const +{ + if ( isNull() || isArray() || isObject() ) + return size() == 0u; + else + return false; +} + + +bool +Value::operator!() const +{ + return isNull(); +} + + +void +Value::clear() +{ + JSON_ASSERT( type_ == nullValue || type_ == arrayValue || type_ == objectValue ); + + switch ( type_ ) + { +#ifndef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + case objectValue: + value_.map_->clear(); + break; +#else + case arrayValue: + value_.array_->clear(); + break; + case objectValue: + value_.map_->clear(); + break; +#endif + default: + break; + } +} + +void +Value::resize( ArrayIndex newSize ) +{ + JSON_ASSERT( type_ == nullValue || type_ == arrayValue ); + if ( type_ == nullValue ) + *this = Value( arrayValue ); +#ifndef JSON_VALUE_USE_INTERNAL_MAP + ArrayIndex oldSize = size(); + if ( newSize == 0 ) + clear(); + else if ( newSize > oldSize ) + (*this)[ newSize - 1 ]; + else + { + for ( ArrayIndex index = newSize; index < oldSize; ++index ) + { + value_.map_->erase( index ); + } + assert( size() == newSize ); + } +#else + value_.array_->resize( newSize ); +#endif +} + + +Value & +Value::operator[]( ArrayIndex index ) +{ + JSON_ASSERT( type_ == nullValue || type_ == arrayValue ); + if ( type_ == nullValue ) + *this = Value( arrayValue ); +#ifndef JSON_VALUE_USE_INTERNAL_MAP + CZString key( index ); + ObjectValues::iterator it = value_.map_->lower_bound( key ); + if ( it != value_.map_->end() && (*it).first == key ) + return (*it).second; + + ObjectValues::value_type defaultValue( key, null ); + it = value_.map_->insert( it, defaultValue ); + return (*it).second; +#else + return value_.array_->resolveReference( index ); +#endif +} + + +Value & +Value::operator[]( int index ) +{ + JSON_ASSERT( index >= 0 ); + return (*this)[ ArrayIndex(index) ]; +} + + +const Value & +Value::operator[]( ArrayIndex index ) const +{ + JSON_ASSERT( type_ == nullValue || type_ == arrayValue ); + if ( type_ == nullValue ) + return null; +#ifndef JSON_VALUE_USE_INTERNAL_MAP + CZString key( index ); + ObjectValues::const_iterator it = value_.map_->find( key ); + if ( it == value_.map_->end() ) + return null; + return (*it).second; +#else + Value *value = value_.array_->find( index ); + return value ? *value : null; +#endif +} + + +const Value & +Value::operator[]( int index ) const +{ + JSON_ASSERT( index >= 0 ); + return (*this)[ ArrayIndex(index) ]; +} + + +Value & +Value::operator[]( const char *key ) +{ + return resolveReference( key, false ); +} + + +Value & +Value::resolveReference( const char *key, + bool isStatic ) +{ + JSON_ASSERT( type_ == nullValue || type_ == objectValue ); + if ( type_ == nullValue ) + *this = Value( objectValue ); +#ifndef JSON_VALUE_USE_INTERNAL_MAP + CZString actualKey( key, isStatic ? CZString::noDuplication + : CZString::duplicateOnCopy ); + ObjectValues::iterator it = value_.map_->lower_bound( actualKey ); + if ( it != value_.map_->end() && (*it).first == actualKey ) + return (*it).second; + + ObjectValues::value_type defaultValue( actualKey, null ); + it = value_.map_->insert( it, defaultValue ); + Value &value = (*it).second; + return value; +#else + return value_.map_->resolveReference( key, isStatic ); +#endif +} + + +Value +Value::get( ArrayIndex index, + const Value &defaultValue ) const +{ + const Value *value = &((*this)[index]); + return value == &null ? defaultValue : *value; +} + + +bool +Value::isValidIndex( ArrayIndex index ) const +{ + return index < size(); +} + + + +const Value & +Value::operator[]( const char *key ) const +{ + JSON_ASSERT( type_ == nullValue || type_ == objectValue ); + if ( type_ == nullValue ) + return null; +#ifndef JSON_VALUE_USE_INTERNAL_MAP + CZString actualKey( key, CZString::noDuplication ); + ObjectValues::const_iterator it = value_.map_->find( actualKey ); + if ( it == value_.map_->end() ) + return null; + return (*it).second; +#else + const Value *value = value_.map_->find( key ); + return value ? *value : null; +#endif +} + + +Value & +Value::operator[]( const std::string &key ) +{ + return (*this)[ key.c_str() ]; +} + + +const Value & +Value::operator[]( const std::string &key ) const +{ + return (*this)[ key.c_str() ]; +} + +Value & +Value::operator[]( const StaticString &key ) +{ + return resolveReference( key, true ); +} + + +# ifdef JSON_USE_CPPTL +Value & +Value::operator[]( const CppTL::ConstString &key ) +{ + return (*this)[ key.c_str() ]; +} + + +const Value & +Value::operator[]( const CppTL::ConstString &key ) const +{ + return (*this)[ key.c_str() ]; +} +# endif + + +Value & +Value::append( const Value &value ) +{ + return (*this)[size()] = value; +} + + +Value +Value::get( const char *key, + const Value &defaultValue ) const +{ + const Value *value = &((*this)[key]); + return value == &null ? defaultValue : *value; +} + + +Value +Value::get( const std::string &key, + const Value &defaultValue ) const +{ + return get( key.c_str(), defaultValue ); +} + +Value +Value::removeMember( const char* key ) +{ + JSON_ASSERT( type_ == nullValue || type_ == objectValue ); + if ( type_ == nullValue ) + return null; +#ifndef JSON_VALUE_USE_INTERNAL_MAP + CZString actualKey( key, CZString::noDuplication ); + ObjectValues::iterator it = value_.map_->find( actualKey ); + if ( it == value_.map_->end() ) + return null; + Value old(it->second); + value_.map_->erase(it); + return old; +#else + Value *value = value_.map_->find( key ); + if (value){ + Value old(*value); + value_.map_.remove( key ); + return old; + } else { + return null; + } +#endif +} + +Value +Value::removeMember( const std::string &key ) +{ + return removeMember( key.c_str() ); +} + +# ifdef JSON_USE_CPPTL +Value +Value::get( const CppTL::ConstString &key, + const Value &defaultValue ) const +{ + return get( key.c_str(), defaultValue ); +} +# endif + +bool +Value::isMember( const char *key ) const +{ + const Value *value = &((*this)[key]); + return value != &null; +} + + +bool +Value::isMember( const std::string &key ) const +{ + return isMember( key.c_str() ); +} + + +# ifdef JSON_USE_CPPTL +bool +Value::isMember( const CppTL::ConstString &key ) const +{ + return isMember( key.c_str() ); +} +#endif + +Value::Members +Value::getMemberNames() const +{ + JSON_ASSERT( type_ == nullValue || type_ == objectValue ); + if ( type_ == nullValue ) + return Value::Members(); + Members members; + members.reserve( value_.map_->size() ); +#ifndef JSON_VALUE_USE_INTERNAL_MAP + ObjectValues::const_iterator it = value_.map_->begin(); + ObjectValues::const_iterator itEnd = value_.map_->end(); + for ( ; it != itEnd; ++it ) + members.push_back( std::string( (*it).first.c_str() ) ); +#else + ValueInternalMap::IteratorState it; + ValueInternalMap::IteratorState itEnd; + value_.map_->makeBeginIterator( it ); + value_.map_->makeEndIterator( itEnd ); + for ( ; !ValueInternalMap::equals( it, itEnd ); ValueInternalMap::increment(it) ) + members.push_back( std::string( ValueInternalMap::key( it ) ) ); +#endif + return members; +} +// +//# ifdef JSON_USE_CPPTL +//EnumMemberNames +//Value::enumMemberNames() const +//{ +// if ( type_ == objectValue ) +// { +// return CppTL::Enum::any( CppTL::Enum::transform( +// CppTL::Enum::keys( *(value_.map_), CppTL::Type() ), +// MemberNamesTransform() ) ); +// } +// return EnumMemberNames(); +//} +// +// +//EnumValues +//Value::enumValues() const +//{ +// if ( type_ == objectValue || type_ == arrayValue ) +// return CppTL::Enum::anyValues( *(value_.map_), +// CppTL::Type() ); +// return EnumValues(); +//} +// +//# endif + + +bool +Value::isNull() const +{ + return type_ == nullValue; +} + + +bool +Value::isBool() const +{ + return type_ == booleanValue; +} + + +bool +Value::isInt() const +{ + return type_ == intValue; +} + + +bool +Value::isUInt() const +{ + return type_ == uintValue; +} + + +bool +Value::isIntegral() const +{ + return type_ == intValue + || type_ == uintValue + || type_ == booleanValue; +} + + +bool +Value::isDouble() const +{ + return type_ == realValue; +} + + +bool +Value::isNumeric() const +{ + return isIntegral() || isDouble(); +} + + +bool +Value::isString() const +{ + return type_ == stringValue; +} + + +bool +Value::isArray() const +{ + return type_ == nullValue || type_ == arrayValue; +} + + +bool +Value::isObject() const +{ + return type_ == nullValue || type_ == objectValue; +} + + +void +Value::setComment( const char *comment, + CommentPlacement placement ) +{ + if ( !comments_ ) + comments_ = new CommentInfo[numberOfCommentPlacement]; + comments_[placement].setComment( comment ); +} + + +void +Value::setComment( const std::string &comment, + CommentPlacement placement ) +{ + setComment( comment.c_str(), placement ); +} + + +bool +Value::hasComment( CommentPlacement placement ) const +{ + return comments_ != 0 && comments_[placement].comment_ != 0; +} + +std::string +Value::getComment( CommentPlacement placement ) const +{ + if ( hasComment(placement) ) + return comments_[placement].comment_; + return ""; +} + + +std::string +Value::toStyledString() const +{ + StyledWriter writer; + return writer.write( *this ); +} + + +Value::const_iterator +Value::begin() const +{ + switch ( type_ ) + { +#ifdef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + if ( value_.array_ ) + { + ValueInternalArray::IteratorState it; + value_.array_->makeBeginIterator( it ); + return const_iterator( it ); + } + break; + case objectValue: + if ( value_.map_ ) + { + ValueInternalMap::IteratorState it; + value_.map_->makeBeginIterator( it ); + return const_iterator( it ); + } + break; +#else + case arrayValue: + case objectValue: + if ( value_.map_ ) + return const_iterator( value_.map_->begin() ); + break; +#endif + default: + break; + } + return const_iterator(); +} + +Value::const_iterator +Value::end() const +{ + switch ( type_ ) + { +#ifdef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + if ( value_.array_ ) + { + ValueInternalArray::IteratorState it; + value_.array_->makeEndIterator( it ); + return const_iterator( it ); + } + break; + case objectValue: + if ( value_.map_ ) + { + ValueInternalMap::IteratorState it; + value_.map_->makeEndIterator( it ); + return const_iterator( it ); + } + break; +#else + case arrayValue: + case objectValue: + if ( value_.map_ ) + return const_iterator( value_.map_->end() ); + break; +#endif + default: + break; + } + return const_iterator(); +} + + +Value::iterator +Value::begin() +{ + switch ( type_ ) + { +#ifdef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + if ( value_.array_ ) + { + ValueInternalArray::IteratorState it; + value_.array_->makeBeginIterator( it ); + return iterator( it ); + } + break; + case objectValue: + if ( value_.map_ ) + { + ValueInternalMap::IteratorState it; + value_.map_->makeBeginIterator( it ); + return iterator( it ); + } + break; +#else + case arrayValue: + case objectValue: + if ( value_.map_ ) + return iterator( value_.map_->begin() ); + break; +#endif + default: + break; + } + return iterator(); +} + +Value::iterator +Value::end() +{ + switch ( type_ ) + { +#ifdef JSON_VALUE_USE_INTERNAL_MAP + case arrayValue: + if ( value_.array_ ) + { + ValueInternalArray::IteratorState it; + value_.array_->makeEndIterator( it ); + return iterator( it ); + } + break; + case objectValue: + if ( value_.map_ ) + { + ValueInternalMap::IteratorState it; + value_.map_->makeEndIterator( it ); + return iterator( it ); + } + break; +#else + case arrayValue: + case objectValue: + if ( value_.map_ ) + return iterator( value_.map_->end() ); + break; +#endif + default: + break; + } + return iterator(); +} + + +// class PathArgument +// ////////////////////////////////////////////////////////////////// + +PathArgument::PathArgument() + : kind_( kindNone ) +{ +} + + +PathArgument::PathArgument( ArrayIndex index ) + : index_( index ) + , kind_( kindIndex ) +{ +} + + +PathArgument::PathArgument( const char *key ) + : key_( key ) + , kind_( kindKey ) +{ +} + + +PathArgument::PathArgument( const std::string &key ) + : key_( key.c_str() ) + , kind_( kindKey ) +{ +} + +// class Path +// ////////////////////////////////////////////////////////////////// + +Path::Path( const std::string &path, + const PathArgument &a1, + const PathArgument &a2, + const PathArgument &a3, + const PathArgument &a4, + const PathArgument &a5 ) +{ + InArgs in; + in.push_back( &a1 ); + in.push_back( &a2 ); + in.push_back( &a3 ); + in.push_back( &a4 ); + in.push_back( &a5 ); + makePath( path, in ); +} + + +void +Path::makePath( const std::string &path, + const InArgs &in ) +{ + const char *current = path.c_str(); + const char *end = current + path.length(); + InArgs::const_iterator itInArg = in.begin(); + while ( current != end ) + { + if ( *current == '[' ) + { + ++current; + if ( *current == '%' ) + addPathInArg( path, in, itInArg, PathArgument::kindIndex ); + else + { + ArrayIndex index = 0; + for ( ; current != end && *current >= '0' && *current <= '9'; ++current ) + index = index * 10 + ArrayIndex(*current - '0'); + args_.push_back( index ); + } + if ( current == end || *current++ != ']' ) + invalidPath( path, int(current - path.c_str()) ); + } + else if ( *current == '%' ) + { + addPathInArg( path, in, itInArg, PathArgument::kindKey ); + ++current; + } + else if ( *current == '.' ) + { + ++current; + } + else + { + const char *beginName = current; + while ( current != end && !strchr( "[.", *current ) ) + ++current; + args_.push_back( std::string( beginName, current ) ); + } + } +} + + +void +Path::addPathInArg( const std::string &path, + const InArgs &in, + InArgs::const_iterator &itInArg, + PathArgument::Kind kind ) +{ + if ( itInArg == in.end() ) + { + // Error: missing argument %d + } + else if ( (*itInArg)->kind_ != kind ) + { + // Error: bad argument type + } + else + { + args_.push_back( **itInArg ); + } +} + + +void +Path::invalidPath( const std::string &path, + int location ) +{ + // Error: invalid path. +} + + +const Value & +Path::resolve( const Value &root ) const +{ + const Value *node = &root; + for ( Args::const_iterator it = args_.begin(); it != args_.end(); ++it ) + { + const PathArgument &arg = *it; + if ( arg.kind_ == PathArgument::kindIndex ) + { + if ( !node->isArray() || node->isValidIndex( arg.index_ ) ) + { + // Error: unable to resolve path (array value expected at position... + } + node = &((*node)[arg.index_]); + } + else if ( arg.kind_ == PathArgument::kindKey ) + { + if ( !node->isObject() ) + { + // Error: unable to resolve path (object value expected at position...) + } + node = &((*node)[arg.key_]); + if ( node == &Value::null ) + { + // Error: unable to resolve path (object has no member named '' at position...) + } + } + } + return *node; +} + + +Value +Path::resolve( const Value &root, + const Value &defaultValue ) const +{ + const Value *node = &root; + for ( Args::const_iterator it = args_.begin(); it != args_.end(); ++it ) + { + const PathArgument &arg = *it; + if ( arg.kind_ == PathArgument::kindIndex ) + { + if ( !node->isArray() || node->isValidIndex( arg.index_ ) ) + return defaultValue; + node = &((*node)[arg.index_]); + } + else if ( arg.kind_ == PathArgument::kindKey ) + { + if ( !node->isObject() ) + return defaultValue; + node = &((*node)[arg.key_]); + if ( node == &Value::null ) + return defaultValue; + } + } + return *node; +} + + +Value & +Path::make( Value &root ) const +{ + Value *node = &root; + for ( Args::const_iterator it = args_.begin(); it != args_.end(); ++it ) + { + const PathArgument &arg = *it; + if ( arg.kind_ == PathArgument::kindIndex ) + { + if ( !node->isArray() ) + { + // Error: node is not an array at position ... + } + node = &((*node)[arg.index_]); + } + else if ( arg.kind_ == PathArgument::kindKey ) + { + if ( !node->isObject() ) + { + // Error: node is not an object at position... + } + node = &((*node)[arg.key_]); + } + } + return *node; +} + + +} // namespace Json diff --git a/deps/src/jsoncpp/json_valueiterator.inl b/deps/src/jsoncpp/json_valueiterator.inl new file mode 100644 index 000000000..7457ca389 --- /dev/null +++ b/deps/src/jsoncpp/json_valueiterator.inl @@ -0,0 +1,299 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +// included by json_value.cpp + +namespace Json { + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueIteratorBase +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueIteratorBase::ValueIteratorBase() +#ifndef JSON_VALUE_USE_INTERNAL_MAP + : current_() + , isNull_( true ) +{ +} +#else + : isArray_( true ) + , isNull_( true ) +{ + iterator_.array_ = ValueInternalArray::IteratorState(); +} +#endif + + +#ifndef JSON_VALUE_USE_INTERNAL_MAP +ValueIteratorBase::ValueIteratorBase( const Value::ObjectValues::iterator ¤t ) + : current_( current ) + , isNull_( false ) +{ +} +#else +ValueIteratorBase::ValueIteratorBase( const ValueInternalArray::IteratorState &state ) + : isArray_( true ) +{ + iterator_.array_ = state; +} + + +ValueIteratorBase::ValueIteratorBase( const ValueInternalMap::IteratorState &state ) + : isArray_( false ) +{ + iterator_.map_ = state; +} +#endif + +Value & +ValueIteratorBase::deref() const +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + return current_->second; +#else + if ( isArray_ ) + return ValueInternalArray::dereference( iterator_.array_ ); + return ValueInternalMap::value( iterator_.map_ ); +#endif +} + + +void +ValueIteratorBase::increment() +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + ++current_; +#else + if ( isArray_ ) + ValueInternalArray::increment( iterator_.array_ ); + ValueInternalMap::increment( iterator_.map_ ); +#endif +} + + +void +ValueIteratorBase::decrement() +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + --current_; +#else + if ( isArray_ ) + ValueInternalArray::decrement( iterator_.array_ ); + ValueInternalMap::decrement( iterator_.map_ ); +#endif +} + + +ValueIteratorBase::difference_type +ValueIteratorBase::computeDistance( const SelfType &other ) const +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP +# ifdef JSON_USE_CPPTL_SMALLMAP + return current_ - other.current_; +# else + // Iterator for null value are initialized using the default + // constructor, which initialize current_ to the default + // std::map::iterator. As begin() and end() are two instance + // of the default std::map::iterator, they can not be compared. + // To allow this, we handle this comparison specifically. + if ( isNull_ && other.isNull_ ) + { + return 0; + } + + + // Usage of std::distance is not portable (does not compile with Sun Studio 12 RogueWave STL, + // which is the one used by default). + // Using a portable hand-made version for non random iterator instead: + // return difference_type( std::distance( current_, other.current_ ) ); + difference_type myDistance = 0; + for ( Value::ObjectValues::iterator it = current_; it != other.current_; ++it ) + { + ++myDistance; + } + return myDistance; +# endif +#else + if ( isArray_ ) + return ValueInternalArray::distance( iterator_.array_, other.iterator_.array_ ); + return ValueInternalMap::distance( iterator_.map_, other.iterator_.map_ ); +#endif +} + + +bool +ValueIteratorBase::isEqual( const SelfType &other ) const +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + if ( isNull_ ) + { + return other.isNull_; + } + return current_ == other.current_; +#else + if ( isArray_ ) + return ValueInternalArray::equals( iterator_.array_, other.iterator_.array_ ); + return ValueInternalMap::equals( iterator_.map_, other.iterator_.map_ ); +#endif +} + + +void +ValueIteratorBase::copy( const SelfType &other ) +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + current_ = other.current_; +#else + if ( isArray_ ) + iterator_.array_ = other.iterator_.array_; + iterator_.map_ = other.iterator_.map_; +#endif +} + + +Value +ValueIteratorBase::key() const +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + const Value::CZString czstring = (*current_).first; + if ( czstring.c_str() ) + { + if ( czstring.isStaticString() ) + return Value( StaticString( czstring.c_str() ) ); + return Value( czstring.c_str() ); + } + return Value( czstring.index() ); +#else + if ( isArray_ ) + return Value( ValueInternalArray::indexOf( iterator_.array_ ) ); + bool isStatic; + const char *memberName = ValueInternalMap::key( iterator_.map_, isStatic ); + if ( isStatic ) + return Value( StaticString( memberName ) ); + return Value( memberName ); +#endif +} + + +UInt +ValueIteratorBase::index() const +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + const Value::CZString czstring = (*current_).first; + if ( !czstring.c_str() ) + return czstring.index(); + return Value::UInt( -1 ); +#else + if ( isArray_ ) + return Value::UInt( ValueInternalArray::indexOf( iterator_.array_ ) ); + return Value::UInt( -1 ); +#endif +} + + +const char * +ValueIteratorBase::memberName() const +{ +#ifndef JSON_VALUE_USE_INTERNAL_MAP + const char *name = (*current_).first.c_str(); + return name ? name : ""; +#else + if ( !isArray_ ) + return ValueInternalMap::key( iterator_.map_ ); + return ""; +#endif +} + + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueConstIterator +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueConstIterator::ValueConstIterator() +{ +} + + +#ifndef JSON_VALUE_USE_INTERNAL_MAP +ValueConstIterator::ValueConstIterator( const Value::ObjectValues::iterator ¤t ) + : ValueIteratorBase( current ) +{ +} +#else +ValueConstIterator::ValueConstIterator( const ValueInternalArray::IteratorState &state ) + : ValueIteratorBase( state ) +{ +} + +ValueConstIterator::ValueConstIterator( const ValueInternalMap::IteratorState &state ) + : ValueIteratorBase( state ) +{ +} +#endif + +ValueConstIterator & +ValueConstIterator::operator =( const ValueIteratorBase &other ) +{ + copy( other ); + return *this; +} + + +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// class ValueIterator +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////// + +ValueIterator::ValueIterator() +{ +} + + +#ifndef JSON_VALUE_USE_INTERNAL_MAP +ValueIterator::ValueIterator( const Value::ObjectValues::iterator ¤t ) + : ValueIteratorBase( current ) +{ +} +#else +ValueIterator::ValueIterator( const ValueInternalArray::IteratorState &state ) + : ValueIteratorBase( state ) +{ +} + +ValueIterator::ValueIterator( const ValueInternalMap::IteratorState &state ) + : ValueIteratorBase( state ) +{ +} +#endif + +ValueIterator::ValueIterator( const ValueConstIterator &other ) + : ValueIteratorBase( other ) +{ +} + +ValueIterator::ValueIterator( const ValueIterator &other ) + : ValueIteratorBase( other ) +{ +} + +ValueIterator & +ValueIterator::operator =( const SelfType &other ) +{ + copy( other ); + return *this; +} + +} // namespace Json diff --git a/deps/src/jsoncpp/json_writer.cpp b/deps/src/jsoncpp/json_writer.cpp new file mode 100644 index 000000000..88ab04fa3 --- /dev/null +++ b/deps/src/jsoncpp/json_writer.cpp @@ -0,0 +1,838 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#if !defined(JSON_IS_AMALGAMATION) +# include "json/writer.h" +# include "json_tool.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include +#include +#include + +#if _MSC_VER >= 1400 // VC++ 8.0 +#pragma warning( disable : 4996 ) // disable warning about strdup being deprecated. +#endif + +namespace Json { + +static bool containsControlCharacter( const char* str ) +{ + while ( *str ) + { + if ( isControlCharacter( *(str++) ) ) + return true; + } + return false; +} + + +std::string valueToString( LargestInt value ) +{ + UIntToStringBuffer buffer; + char *current = buffer + sizeof(buffer); + bool isNegative = value < 0; + if ( isNegative ) + value = -value; + uintToString( LargestUInt(value), current ); + if ( isNegative ) + *--current = '-'; + assert( current >= buffer ); + return current; +} + + +std::string valueToString( LargestUInt value ) +{ + UIntToStringBuffer buffer; + char *current = buffer + sizeof(buffer); + uintToString( value, current ); + assert( current >= buffer ); + return current; +} + +#if defined(JSON_HAS_INT64) + +std::string valueToString( Int value ) +{ + return valueToString( LargestInt(value) ); +} + + +std::string valueToString( UInt value ) +{ + return valueToString( LargestUInt(value) ); +} + +#endif // # if defined(JSON_HAS_INT64) + + +std::string valueToString( double value ) +{ + char buffer[32]; +#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 to avoid warning. + sprintf_s(buffer, sizeof(buffer), "%#.16g", value); +#else + sprintf(buffer, "%#.16g", value); +#endif + char* ch = buffer + strlen(buffer) - 1; + if (*ch != '0') return buffer; // nothing to truncate, so save time + while(ch > buffer && *ch == '0'){ + --ch; + } + char* last_nonzero = ch; + while(ch >= buffer){ + switch(*ch){ + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + --ch; + continue; + case '.': + // Truncate zeroes to save bytes in output, but keep one. + *(last_nonzero+2) = '\0'; + return buffer; + default: + return buffer; + } + } + return buffer; +} + + +std::string valueToString( bool value ) +{ + return value ? "true" : "false"; +} + +std::string valueToQuotedString( const char *value ) +{ + // Not sure how to handle unicode... + if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && !containsControlCharacter( value )) + return std::string("\"") + value + "\""; + // We have to walk value and escape any special characters. + // Appending to std::string is not efficient, but this should be rare. + // (Note: forward slashes are *not* rare, but I am not escaping them.) + std::string::size_type maxsize = strlen(value)*2 + 3; // allescaped+quotes+NULL + std::string result; + result.reserve(maxsize); // to avoid lots of mallocs + result += "\""; + for (const char* c=value; *c != 0; ++c) + { + switch(*c) + { + case '\"': + result += "\\\""; + break; + case '\\': + result += "\\\\"; + break; + case '\b': + result += "\\b"; + break; + case '\f': + result += "\\f"; + break; + case '\n': + result += "\\n"; + break; + case '\r': + result += "\\r"; + break; + case '\t': + result += "\\t"; + break; + //case '/': + // Even though \/ is considered a legal escape in JSON, a bare + // slash is also legal, so I see no reason to escape it. + // (I hope I am not misunderstanding something. + // blep notes: actually escaping \/ may be useful in javascript to avoid (*c); + result += oss.str(); + } + else + { + result += *c; + } + break; + } + } + result += "\""; + return result; +} + +// Class Writer +// ////////////////////////////////////////////////////////////////// +Writer::~Writer() +{ +} + + +// Class FastWriter +// ////////////////////////////////////////////////////////////////// + +FastWriter::FastWriter() + : yamlCompatiblityEnabled_( false ) +{ +} + + +void +FastWriter::enableYAMLCompatibility() +{ + yamlCompatiblityEnabled_ = true; +} + + +std::string +FastWriter::write( const Value &root ) +{ + document_ = ""; + writeValue( root ); + document_ += "\n"; + return document_; +} + + +void +FastWriter::writeValue( const Value &value ) +{ + switch ( value.type() ) + { + case nullValue: + document_ += "null"; + break; + case intValue: + document_ += valueToString( value.asLargestInt() ); + break; + case uintValue: + document_ += valueToString( value.asLargestUInt() ); + break; + case realValue: + document_ += valueToString( value.asDouble() ); + break; + case stringValue: + document_ += valueToQuotedString( value.asCString() ); + break; + case booleanValue: + document_ += valueToString( value.asBool() ); + break; + case arrayValue: + { + document_ += "["; + int size = value.size(); + for ( int index =0; index < size; ++index ) + { + if ( index > 0 ) + document_ += ","; + writeValue( value[index] ); + } + document_ += "]"; + } + break; + case objectValue: + { + Value::Members members( value.getMemberNames() ); + document_ += "{"; + for ( Value::Members::iterator it = members.begin(); + it != members.end(); + ++it ) + { + const std::string &name = *it; + if ( it != members.begin() ) + document_ += ","; + document_ += valueToQuotedString( name.c_str() ); + document_ += yamlCompatiblityEnabled_ ? ": " + : ":"; + writeValue( value[name] ); + } + document_ += "}"; + } + break; + } +} + + +// Class StyledWriter +// ////////////////////////////////////////////////////////////////// + +StyledWriter::StyledWriter() + : rightMargin_( 74 ) + , indentSize_( 3 ) +{ +} + + +std::string +StyledWriter::write( const Value &root ) +{ + document_ = ""; + addChildValues_ = false; + indentString_ = ""; + writeCommentBeforeValue( root ); + writeValue( root ); + writeCommentAfterValueOnSameLine( root ); + document_ += "\n"; + return document_; +} + + +void +StyledWriter::writeValue( const Value &value ) +{ + switch ( value.type() ) + { + case nullValue: + pushValue( "null" ); + break; + case intValue: + pushValue( valueToString( value.asLargestInt() ) ); + break; + case uintValue: + pushValue( valueToString( value.asLargestUInt() ) ); + break; + case realValue: + pushValue( valueToString( value.asDouble() ) ); + break; + case stringValue: + pushValue( valueToQuotedString( value.asCString() ) ); + break; + case booleanValue: + pushValue( valueToString( value.asBool() ) ); + break; + case arrayValue: + writeArrayValue( value); + break; + case objectValue: + { + Value::Members members( value.getMemberNames() ); + if ( members.empty() ) + pushValue( "{}" ); + else + { + writeWithIndent( "{" ); + indent(); + Value::Members::iterator it = members.begin(); + for (;;) + { + const std::string &name = *it; + const Value &childValue = value[name]; + writeCommentBeforeValue( childValue ); + writeWithIndent( valueToQuotedString( name.c_str() ) ); + document_ += " : "; + writeValue( childValue ); + if ( ++it == members.end() ) + { + writeCommentAfterValueOnSameLine( childValue ); + break; + } + document_ += ","; + writeCommentAfterValueOnSameLine( childValue ); + } + unindent(); + writeWithIndent( "}" ); + } + } + break; + } +} + + +void +StyledWriter::writeArrayValue( const Value &value ) +{ + unsigned size = value.size(); + if ( size == 0 ) + pushValue( "[]" ); + else + { + bool isArrayMultiLine = isMultineArray( value ); + if ( isArrayMultiLine ) + { + writeWithIndent( "[" ); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index =0; + for (;;) + { + const Value &childValue = value[index]; + writeCommentBeforeValue( childValue ); + if ( hasChildValue ) + writeWithIndent( childValues_[index] ); + else + { + writeIndent(); + writeValue( childValue ); + } + if ( ++index == size ) + { + writeCommentAfterValueOnSameLine( childValue ); + break; + } + document_ += ","; + writeCommentAfterValueOnSameLine( childValue ); + } + unindent(); + writeWithIndent( "]" ); + } + else // output on a single line + { + assert( childValues_.size() == size ); + document_ += "[ "; + for ( unsigned index =0; index < size; ++index ) + { + if ( index > 0 ) + document_ += ", "; + document_ += childValues_[index]; + } + document_ += " ]"; + } + } +} + + +bool +StyledWriter::isMultineArray( const Value &value ) +{ + int size = value.size(); + bool isMultiLine = size*3 >= rightMargin_ ; + childValues_.clear(); + for ( int index =0; index < size && !isMultiLine; ++index ) + { + const Value &childValue = value[index]; + isMultiLine = isMultiLine || + ( (childValue.isArray() || childValue.isObject()) && + childValue.size() > 0 ); + } + if ( !isMultiLine ) // check if line length > max line length + { + childValues_.reserve( size ); + addChildValues_ = true; + int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]' + for ( int index =0; index < size && !isMultiLine; ++index ) + { + writeValue( value[index] ); + lineLength += int( childValues_[index].length() ); + isMultiLine = isMultiLine && hasCommentForValue( value[index] ); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + + +void +StyledWriter::pushValue( const std::string &value ) +{ + if ( addChildValues_ ) + childValues_.push_back( value ); + else + document_ += value; +} + + +void +StyledWriter::writeIndent() +{ + if ( !document_.empty() ) + { + char last = document_[document_.length()-1]; + if ( last == ' ' ) // already indented + return; + if ( last != '\n' ) // Comments may add new-line + document_ += '\n'; + } + document_ += indentString_; +} + + +void +StyledWriter::writeWithIndent( const std::string &value ) +{ + writeIndent(); + document_ += value; +} + + +void +StyledWriter::indent() +{ + indentString_ += std::string( indentSize_, ' ' ); +} + + +void +StyledWriter::unindent() +{ + assert( int(indentString_.size()) >= indentSize_ ); + indentString_.resize( indentString_.size() - indentSize_ ); +} + + +void +StyledWriter::writeCommentBeforeValue( const Value &root ) +{ + if ( !root.hasComment( commentBefore ) ) + return; + document_ += normalizeEOL( root.getComment( commentBefore ) ); + document_ += "\n"; +} + + +void +StyledWriter::writeCommentAfterValueOnSameLine( const Value &root ) +{ + if ( root.hasComment( commentAfterOnSameLine ) ) + document_ += " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) ); + + if ( root.hasComment( commentAfter ) ) + { + document_ += "\n"; + document_ += normalizeEOL( root.getComment( commentAfter ) ); + document_ += "\n"; + } +} + + +bool +StyledWriter::hasCommentForValue( const Value &value ) +{ + return value.hasComment( commentBefore ) + || value.hasComment( commentAfterOnSameLine ) + || value.hasComment( commentAfter ); +} + + +std::string +StyledWriter::normalizeEOL( const std::string &text ) +{ + std::string normalized; + normalized.reserve( text.length() ); + const char *begin = text.c_str(); + const char *end = begin + text.length(); + const char *current = begin; + while ( current != end ) + { + char c = *current++; + if ( c == '\r' ) // mac or dos EOL + { + if ( *current == '\n' ) // convert dos EOL + ++current; + normalized += '\n'; + } + else // handle unix EOL & other char + normalized += c; + } + return normalized; +} + + +// Class StyledStreamWriter +// ////////////////////////////////////////////////////////////////// + +StyledStreamWriter::StyledStreamWriter( std::string indentation ) + : document_(NULL) + , rightMargin_( 74 ) + , indentation_( indentation ) +{ +} + + +void +StyledStreamWriter::write( std::ostream &out, const Value &root ) +{ + document_ = &out; + addChildValues_ = false; + indentString_ = ""; + writeCommentBeforeValue( root ); + writeValue( root ); + writeCommentAfterValueOnSameLine( root ); + *document_ << "\n"; + document_ = NULL; // Forget the stream, for safety. +} + + +void +StyledStreamWriter::writeValue( const Value &value ) +{ + switch ( value.type() ) + { + case nullValue: + pushValue( "null" ); + break; + case intValue: + pushValue( valueToString( value.asLargestInt() ) ); + break; + case uintValue: + pushValue( valueToString( value.asLargestUInt() ) ); + break; + case realValue: + pushValue( valueToString( value.asDouble() ) ); + break; + case stringValue: + pushValue( valueToQuotedString( value.asCString() ) ); + break; + case booleanValue: + pushValue( valueToString( value.asBool() ) ); + break; + case arrayValue: + writeArrayValue( value); + break; + case objectValue: + { + Value::Members members( value.getMemberNames() ); + if ( members.empty() ) + pushValue( "{}" ); + else + { + writeWithIndent( "{" ); + indent(); + Value::Members::iterator it = members.begin(); + for (;;) + { + const std::string &name = *it; + const Value &childValue = value[name]; + writeCommentBeforeValue( childValue ); + writeWithIndent( valueToQuotedString( name.c_str() ) ); + *document_ << " : "; + writeValue( childValue ); + if ( ++it == members.end() ) + { + writeCommentAfterValueOnSameLine( childValue ); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine( childValue ); + } + unindent(); + writeWithIndent( "}" ); + } + } + break; + } +} + + +void +StyledStreamWriter::writeArrayValue( const Value &value ) +{ + unsigned size = value.size(); + if ( size == 0 ) + pushValue( "[]" ); + else + { + bool isArrayMultiLine = isMultineArray( value ); + if ( isArrayMultiLine ) + { + writeWithIndent( "[" ); + indent(); + bool hasChildValue = !childValues_.empty(); + unsigned index =0; + for (;;) + { + const Value &childValue = value[index]; + writeCommentBeforeValue( childValue ); + if ( hasChildValue ) + writeWithIndent( childValues_[index] ); + else + { + writeIndent(); + writeValue( childValue ); + } + if ( ++index == size ) + { + writeCommentAfterValueOnSameLine( childValue ); + break; + } + *document_ << ","; + writeCommentAfterValueOnSameLine( childValue ); + } + unindent(); + writeWithIndent( "]" ); + } + else // output on a single line + { + assert( childValues_.size() == size ); + *document_ << "[ "; + for ( unsigned index =0; index < size; ++index ) + { + if ( index > 0 ) + *document_ << ", "; + *document_ << childValues_[index]; + } + *document_ << " ]"; + } + } +} + + +bool +StyledStreamWriter::isMultineArray( const Value &value ) +{ + int size = value.size(); + bool isMultiLine = size*3 >= rightMargin_ ; + childValues_.clear(); + for ( int index =0; index < size && !isMultiLine; ++index ) + { + const Value &childValue = value[index]; + isMultiLine = isMultiLine || + ( (childValue.isArray() || childValue.isObject()) && + childValue.size() > 0 ); + } + if ( !isMultiLine ) // check if line length > max line length + { + childValues_.reserve( size ); + addChildValues_ = true; + int lineLength = 4 + (size-1)*2; // '[ ' + ', '*n + ' ]' + for ( int index =0; index < size && !isMultiLine; ++index ) + { + writeValue( value[index] ); + lineLength += int( childValues_[index].length() ); + isMultiLine = isMultiLine && hasCommentForValue( value[index] ); + } + addChildValues_ = false; + isMultiLine = isMultiLine || lineLength >= rightMargin_; + } + return isMultiLine; +} + + +void +StyledStreamWriter::pushValue( const std::string &value ) +{ + if ( addChildValues_ ) + childValues_.push_back( value ); + else + *document_ << value; +} + + +void +StyledStreamWriter::writeIndent() +{ + /* + Some comments in this method would have been nice. ;-) + + if ( !document_.empty() ) + { + char last = document_[document_.length()-1]; + if ( last == ' ' ) // already indented + return; + if ( last != '\n' ) // Comments may add new-line + *document_ << '\n'; + } + */ + *document_ << '\n' << indentString_; +} + + +void +StyledStreamWriter::writeWithIndent( const std::string &value ) +{ + writeIndent(); + *document_ << value; +} + + +void +StyledStreamWriter::indent() +{ + indentString_ += indentation_; +} + + +void +StyledStreamWriter::unindent() +{ + assert( indentString_.size() >= indentation_.size() ); + indentString_.resize( indentString_.size() - indentation_.size() ); +} + + +void +StyledStreamWriter::writeCommentBeforeValue( const Value &root ) +{ + if ( !root.hasComment( commentBefore ) ) + return; + *document_ << normalizeEOL( root.getComment( commentBefore ) ); + *document_ << "\n"; +} + + +void +StyledStreamWriter::writeCommentAfterValueOnSameLine( const Value &root ) +{ + if ( root.hasComment( commentAfterOnSameLine ) ) + *document_ << " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) ); + + if ( root.hasComment( commentAfter ) ) + { + *document_ << "\n"; + *document_ << normalizeEOL( root.getComment( commentAfter ) ); + *document_ << "\n"; + } +} + + +bool +StyledStreamWriter::hasCommentForValue( const Value &value ) +{ + return value.hasComment( commentBefore ) + || value.hasComment( commentAfterOnSameLine ) + || value.hasComment( commentAfter ); +} + + +std::string +StyledStreamWriter::normalizeEOL( const std::string &text ) +{ + std::string normalized; + normalized.reserve( text.length() ); + const char *begin = text.c_str(); + const char *end = begin + text.length(); + const char *current = begin; + while ( current != end ) + { + char c = *current++; + if ( c == '\r' ) // mac or dos EOL + { + if ( *current == '\n' ) // convert dos EOL + ++current; + normalized += '\n'; + } + else // handle unix EOL & other char + normalized += c; + } + return normalized; +} + + +std::ostream& operator<<( std::ostream &sout, const Value &root ) +{ + Json::StyledStreamWriter writer; + writer.write(sout, root); + return sout; +} + + +} // namespace Json diff --git a/third_party/pugixml/pugiconfig.hpp b/deps/src/pugixml/pugiconfig.hpp similarity index 91% rename from third_party/pugixml/pugiconfig.hpp rename to deps/src/pugixml/pugiconfig.hpp index 312c6585f..18e310a39 100644 --- a/third_party/pugixml/pugiconfig.hpp +++ b/deps/src/pugixml/pugiconfig.hpp @@ -1,69 +1,72 @@ -/** - * pugixml parser - version 1.2 - * -------------------------------------------------------- - * Copyright (C) 2006-2012, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) - * Report bugs and download new versions at http://pugixml.org/ - * - * This library is distributed under the MIT License. See notice at the end - * of this file. - * - * This work is based on the pugxml parser, which is: - * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) - */ - -#ifndef HEADER_PUGICONFIG_HPP -#define HEADER_PUGICONFIG_HPP - -// Uncomment this to enable wchar_t mode -#define PUGIXML_WCHAR_MODE - -// Uncomment this to disable XPath -// #define PUGIXML_NO_XPATH - -// Uncomment this to disable STL -// #define PUGIXML_NO_STL - -// Uncomment this to disable exceptions -// #define PUGIXML_NO_EXCEPTIONS - -// Set this to control attributes for public classes/functions, i.e.: -// #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL -// #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL -// #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall -// In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead - -// Uncomment this to switch to header-only version -// #define PUGIXML_HEADER_ONLY -// #include "pugixml.cpp" - -// Tune these constants to adjust memory-related behavior -// #define PUGIXML_MEMORY_PAGE_SIZE 32768 -// #define PUGIXML_MEMORY_OUTPUT_STACK 10240 -// #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096 - -#endif - -/** - * Copyright (c) 2006-2012 Arseny Kapoulkine - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ +/** + * pugixml parser - version 1.4 + * -------------------------------------------------------- + * Copyright (C) 2006-2014, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Report bugs and download new versions at http://pugixml.org/ + * + * This library is distributed under the MIT License. See notice at the end + * of this file. + * + * This work is based on the pugxml parser, which is: + * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) + */ + +#ifndef HEADER_PUGICONFIG_HPP +#define HEADER_PUGICONFIG_HPP + +// Uncomment this to enable wchar_t mode +#define PUGIXML_WCHAR_MODE + +// Uncomment this to disable XPath +// #define PUGIXML_NO_XPATH + +// Uncomment this to disable STL +// #define PUGIXML_NO_STL + +// Uncomment this to disable exceptions +// #define PUGIXML_NO_EXCEPTIONS + +// Set this to control attributes for public classes/functions, i.e.: +// #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL +// #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL +// #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall +// In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead + +// Tune these constants to adjust memory-related behavior +// #define PUGIXML_MEMORY_PAGE_SIZE 32768 +// #define PUGIXML_MEMORY_OUTPUT_STACK 10240 +// #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096 + +// Uncomment this to switch to header-only version +// #define PUGIXML_HEADER_ONLY +// #include "pugixml.cpp" + +// Uncomment this to enable long long support +// #define PUGIXML_HAS_LONG_LONG + +#endif + +/** + * Copyright (c) 2006-2014 Arseny Kapoulkine + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ diff --git a/third_party/pugixml/pugixml.cpp b/deps/src/pugixml/pugixml.cpp similarity index 90% rename from third_party/pugixml/pugixml.cpp rename to deps/src/pugixml/pugixml.cpp index 642d92d38..754f92fea 100644 --- a/third_party/pugixml/pugixml.cpp +++ b/deps/src/pugixml/pugixml.cpp @@ -1,10250 +1,10639 @@ -/** - * pugixml parser - version 1.2 - * -------------------------------------------------------- - * Copyright (C) 2006-2012, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) - * Report bugs and download new versions at http://pugixml.org/ - * - * This library is distributed under the MIT License. See notice at the end - * of this file. - * - * This work is based on the pugxml parser, which is: - * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) - */ - -#ifndef SOURCE_PUGIXML_CPP -#define SOURCE_PUGIXML_CPP - -#include "pugixml.hpp" - -#include -#include -#include -#include -#include - -#ifndef PUGIXML_NO_XPATH -# include -# include -# ifdef PUGIXML_NO_EXCEPTIONS -# include -# endif -#endif - -#ifndef PUGIXML_NO_STL -# include -# include -# include -#endif - -// For placement new -#include - -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable: 4127) // conditional expression is constant -# pragma warning(disable: 4324) // structure was padded due to __declspec(align()) -# pragma warning(disable: 4611) // interaction between '_setjmp' and C++ object destruction is non-portable -# pragma warning(disable: 4702) // unreachable code -# pragma warning(disable: 4996) // this function or variable may be unsafe -# pragma warning(disable: 4793) // function compiled as native: presence of '_setjmp' makes a function unmanaged -#endif - -#ifdef __INTEL_COMPILER -# pragma warning(disable: 177) // function was declared but never referenced -# pragma warning(disable: 279) // controlling expression is constant -# pragma warning(disable: 1478 1786) // function was declared "deprecated" -# pragma warning(disable: 1684) // conversion from pointer to same-sized integral type -#endif - -#if defined(__BORLANDC__) && defined(PUGIXML_HEADER_ONLY) -# pragma warn -8080 // symbol is declared but never used; disabling this inside push/pop bracket does not make the warning go away -#endif - -#ifdef __BORLANDC__ -# pragma option push -# pragma warn -8008 // condition is always false -# pragma warn -8066 // unreachable code -#endif - -#ifdef __SNC__ -// Using diag_push/diag_pop does not disable the warnings inside templates due to a compiler bug -# pragma diag_suppress=178 // function was declared but never referenced -# pragma diag_suppress=237 // controlling expression is constant -#endif - -// Inlining controls -#if defined(_MSC_VER) && _MSC_VER >= 1300 -# define PUGI__NO_INLINE __declspec(noinline) -#elif defined(__GNUC__) -# define PUGI__NO_INLINE __attribute__((noinline)) -#else -# define PUGI__NO_INLINE -#endif - -// Simple static assertion -#define PUGI__STATIC_ASSERT(cond) { static const char condition_failed[(cond) ? 1 : -1] = {0}; (void)condition_failed[0]; } - -// Digital Mars C++ bug workaround for passing char loaded from memory via stack -#ifdef __DMC__ -# define PUGI__DMC_VOLATILE volatile -#else -# define PUGI__DMC_VOLATILE -#endif - -// Borland C++ bug workaround for not defining ::memcpy depending on header include order (can't always use std::memcpy because some compilers don't have it at all) -#if defined(__BORLANDC__) && !defined(__MEM_H_USING_LIST) -using std::memcpy; -using std::memmove; -#endif - -// In some environments MSVC is a compiler but the CRT lacks certain MSVC-specific features -#if defined(_MSC_VER) && !defined(__S3E__) -# define PUGI__MSVC_CRT_VERSION _MSC_VER -#endif - -#ifdef PUGIXML_HEADER_ONLY -# define PUGI__NS_BEGIN namespace pugi { namespace impl { -# define PUGI__NS_END } } -# define PUGI__FN inline -# define PUGI__FN_NO_INLINE inline -#else -# if defined(_MSC_VER) && _MSC_VER < 1300 // MSVC6 seems to have an amusing bug with anonymous namespaces inside namespaces -# define PUGI__NS_BEGIN namespace pugi { namespace impl { -# define PUGI__NS_END } } -# else -# define PUGI__NS_BEGIN namespace pugi { namespace impl { namespace { -# define PUGI__NS_END } } } -# endif -# define PUGI__FN -# define PUGI__FN_NO_INLINE PUGI__NO_INLINE -#endif - -// uintptr_t -#if !defined(_MSC_VER) || _MSC_VER >= 1600 -# include -#else -# ifndef _UINTPTR_T_DEFINED -// No native uintptr_t in MSVC6 and in some WinCE versions -typedef size_t uintptr_t; -#define _UINTPTR_T_DEFINED -# endif -PUGI__NS_BEGIN - typedef unsigned __int8 uint8_t; - typedef unsigned __int16 uint16_t; - typedef unsigned __int32 uint32_t; -PUGI__NS_END -#endif - -// Memory allocation -PUGI__NS_BEGIN - PUGI__FN void* default_allocate(size_t size) - { - return malloc(size); - } - - PUGI__FN void default_deallocate(void* ptr) - { - free(ptr); - } - - template - struct xml_memory_management_function_storage - { - static allocation_function allocate; - static deallocation_function deallocate; - }; - - template allocation_function xml_memory_management_function_storage::allocate = default_allocate; - template deallocation_function xml_memory_management_function_storage::deallocate = default_deallocate; - - typedef xml_memory_management_function_storage xml_memory; -PUGI__NS_END - -// String utilities -PUGI__NS_BEGIN - // Get string length - PUGI__FN size_t strlength(const char_t* s) - { - assert(s); - - #ifdef PUGIXML_WCHAR_MODE - return wcslen(s); - #else - return strlen(s); - #endif - } - - // Compare two strings - PUGI__FN bool strequal(const char_t* src, const char_t* dst) - { - assert(src && dst); - - #ifdef PUGIXML_WCHAR_MODE - return wcscmp(src, dst) == 0; - #else - return strcmp(src, dst) == 0; - #endif - } - - // Compare lhs with [rhs_begin, rhs_end) - PUGI__FN bool strequalrange(const char_t* lhs, const char_t* rhs, size_t count) - { - for (size_t i = 0; i < count; ++i) - if (lhs[i] != rhs[i]) - return false; - - return lhs[count] == 0; - } - -#ifdef PUGIXML_WCHAR_MODE - // Convert string to wide string, assuming all symbols are ASCII - PUGI__FN void widen_ascii(wchar_t* dest, const char* source) - { - for (const char* i = source; *i; ++i) *dest++ = *i; - *dest = 0; - } -#endif -PUGI__NS_END - -#if !defined(PUGIXML_NO_STL) || !defined(PUGIXML_NO_XPATH) -// auto_ptr-like buffer holder for exception recovery -PUGI__NS_BEGIN - struct buffer_holder - { - void* data; - void (*deleter)(void*); - - buffer_holder(void* data_, void (*deleter_)(void*)): data(data_), deleter(deleter_) - { - } - - ~buffer_holder() - { - if (data) deleter(data); - } - - void* release() - { - void* result = data; - data = 0; - return result; - } - }; -PUGI__NS_END -#endif - -PUGI__NS_BEGIN - static const size_t xml_memory_page_size = - #ifdef PUGIXML_MEMORY_PAGE_SIZE - PUGIXML_MEMORY_PAGE_SIZE - #else - 32768 - #endif - ; - - static const uintptr_t xml_memory_page_alignment = 32; - static const uintptr_t xml_memory_page_pointer_mask = ~(xml_memory_page_alignment - 1); - static const uintptr_t xml_memory_page_name_allocated_mask = 16; - static const uintptr_t xml_memory_page_value_allocated_mask = 8; - static const uintptr_t xml_memory_page_type_mask = 7; - - struct xml_allocator; - - struct xml_memory_page - { - static xml_memory_page* construct(void* memory) - { - if (!memory) return 0; //$ redundant, left for performance - - xml_memory_page* result = static_cast(memory); - - result->allocator = 0; - result->memory = 0; - result->prev = 0; - result->next = 0; - result->busy_size = 0; - result->freed_size = 0; - - return result; - } - - xml_allocator* allocator; - - void* memory; - - xml_memory_page* prev; - xml_memory_page* next; - - size_t busy_size; - size_t freed_size; - - char data[1]; - }; - - struct xml_memory_string_header - { - uint16_t page_offset; // offset from page->data - uint16_t full_size; // 0 if string occupies whole page - }; - - struct xml_allocator - { - xml_allocator(xml_memory_page* root): _root(root), _busy_size(root->busy_size) - { - } - - xml_memory_page* allocate_page(size_t data_size) - { - size_t size = offsetof(xml_memory_page, data) + data_size; - - // allocate block with some alignment, leaving memory for worst-case padding - void* memory = xml_memory::allocate(size + xml_memory_page_alignment); - if (!memory) return 0; - - // align upwards to page boundary - void* page_memory = reinterpret_cast((reinterpret_cast(memory) + (xml_memory_page_alignment - 1)) & ~(xml_memory_page_alignment - 1)); - - // prepare page structure - xml_memory_page* page = xml_memory_page::construct(page_memory); - - page->memory = memory; - page->allocator = _root->allocator; - - return page; - } - - static void deallocate_page(xml_memory_page* page) - { - xml_memory::deallocate(page->memory); - } - - void* allocate_memory_oob(size_t size, xml_memory_page*& out_page); - - void* allocate_memory(size_t size, xml_memory_page*& out_page) - { - if (_busy_size + size > xml_memory_page_size) return allocate_memory_oob(size, out_page); - - void* buf = _root->data + _busy_size; - - _busy_size += size; - - out_page = _root; - - return buf; - } - - void deallocate_memory(void* ptr, size_t size, xml_memory_page* page) - { - if (page == _root) page->busy_size = _busy_size; - - assert(ptr >= page->data && ptr < page->data + page->busy_size); - (void)!ptr; - - page->freed_size += size; - assert(page->freed_size <= page->busy_size); - - if (page->freed_size == page->busy_size) - { - if (page->next == 0) - { - assert(_root == page); - - // top page freed, just reset sizes - page->busy_size = page->freed_size = 0; - _busy_size = 0; - } - else - { - assert(_root != page); - assert(page->prev); - - // remove from the list - page->prev->next = page->next; - page->next->prev = page->prev; - - // deallocate - deallocate_page(page); - } - } - } - - char_t* allocate_string(size_t length) - { - // allocate memory for string and header block - size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t); - - // round size up to pointer alignment boundary - size_t full_size = (size + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1); - - xml_memory_page* page; - xml_memory_string_header* header = static_cast(allocate_memory(full_size, page)); - - if (!header) return 0; - - // setup header - ptrdiff_t page_offset = reinterpret_cast(header) - page->data; - - assert(page_offset >= 0 && page_offset < (1 << 16)); - header->page_offset = static_cast(page_offset); - - // full_size == 0 for large strings that occupy the whole page - assert(full_size < (1 << 16) || (page->busy_size == full_size && page_offset == 0)); - header->full_size = static_cast(full_size < (1 << 16) ? full_size : 0); - - // round-trip through void* to avoid 'cast increases required alignment of target type' warning - // header is guaranteed a pointer-sized alignment, which should be enough for char_t - return static_cast(static_cast(header + 1)); - } - - void deallocate_string(char_t* string) - { - // this function casts pointers through void* to avoid 'cast increases required alignment of target type' warnings - // we're guaranteed the proper (pointer-sized) alignment on the input string if it was allocated via allocate_string - - // get header - xml_memory_string_header* header = static_cast(static_cast(string)) - 1; - - // deallocate - size_t page_offset = offsetof(xml_memory_page, data) + header->page_offset; - xml_memory_page* page = reinterpret_cast(static_cast(reinterpret_cast(header) - page_offset)); - - // if full_size == 0 then this string occupies the whole page - size_t full_size = header->full_size == 0 ? page->busy_size : header->full_size; - - deallocate_memory(header, full_size, page); - } - - xml_memory_page* _root; - size_t _busy_size; - }; - - PUGI__FN_NO_INLINE void* xml_allocator::allocate_memory_oob(size_t size, xml_memory_page*& out_page) - { - const size_t large_allocation_threshold = xml_memory_page_size / 4; - - xml_memory_page* page = allocate_page(size <= large_allocation_threshold ? xml_memory_page_size : size); - out_page = page; - - if (!page) return 0; - - if (size <= large_allocation_threshold) - { - _root->busy_size = _busy_size; - - // insert page at the end of linked list - page->prev = _root; - _root->next = page; - _root = page; - - _busy_size = size; - } - else - { - // insert page before the end of linked list, so that it is deleted as soon as possible - // the last page is not deleted even if it's empty (see deallocate_memory) - assert(_root->prev); - - page->prev = _root->prev; - page->next = _root; - - _root->prev->next = page; - _root->prev = page; - } - - // allocate inside page - page->busy_size = size; - - return page->data; - } -PUGI__NS_END - -namespace pugi -{ - /// A 'name=value' XML attribute structure. - struct xml_attribute_struct - { - /// Default ctor - xml_attribute_struct(impl::xml_memory_page* page): header(reinterpret_cast(page)), name(0), value(0), prev_attribute_c(0), next_attribute(0) - { - } - - uintptr_t header; - - char_t* name; ///< Pointer to attribute name. - char_t* value; ///< Pointer to attribute value. - - xml_attribute_struct* prev_attribute_c; ///< Previous attribute (cyclic list) - xml_attribute_struct* next_attribute; ///< Next attribute - }; - - /// An XML document tree node. - struct xml_node_struct - { - /// Default ctor - /// \param type - node type - xml_node_struct(impl::xml_memory_page* page, xml_node_type type): header(reinterpret_cast(page) | (type - 1)), parent(0), name(0), value(0), first_child(0), prev_sibling_c(0), next_sibling(0), first_attribute(0) - { - } - - uintptr_t header; - - xml_node_struct* parent; ///< Pointer to parent - - char_t* name; ///< Pointer to element name. - char_t* value; ///< Pointer to any associated string data. - - xml_node_struct* first_child; ///< First child - - xml_node_struct* prev_sibling_c; ///< Left brother (cyclic list) - xml_node_struct* next_sibling; ///< Right brother - - xml_attribute_struct* first_attribute; ///< First attribute - }; -} - -PUGI__NS_BEGIN - struct xml_document_struct: public xml_node_struct, public xml_allocator - { - xml_document_struct(xml_memory_page* page): xml_node_struct(page, node_document), xml_allocator(page), buffer(0) - { - } - - const char_t* buffer; - }; - - inline xml_allocator& get_allocator(const xml_node_struct* node) - { - assert(node); - - return *reinterpret_cast(node->header & xml_memory_page_pointer_mask)->allocator; - } -PUGI__NS_END - -// Low-level DOM operations -PUGI__NS_BEGIN - inline xml_attribute_struct* allocate_attribute(xml_allocator& alloc) - { - xml_memory_page* page; - void* memory = alloc.allocate_memory(sizeof(xml_attribute_struct), page); - - return new (memory) xml_attribute_struct(page); - } - - inline xml_node_struct* allocate_node(xml_allocator& alloc, xml_node_type type) - { - xml_memory_page* page; - void* memory = alloc.allocate_memory(sizeof(xml_node_struct), page); - - return new (memory) xml_node_struct(page, type); - } - - inline void destroy_attribute(xml_attribute_struct* a, xml_allocator& alloc) - { - uintptr_t header = a->header; - - if (header & impl::xml_memory_page_name_allocated_mask) alloc.deallocate_string(a->name); - if (header & impl::xml_memory_page_value_allocated_mask) alloc.deallocate_string(a->value); - - alloc.deallocate_memory(a, sizeof(xml_attribute_struct), reinterpret_cast(header & xml_memory_page_pointer_mask)); - } - - inline void destroy_node(xml_node_struct* n, xml_allocator& alloc) - { - uintptr_t header = n->header; - - if (header & impl::xml_memory_page_name_allocated_mask) alloc.deallocate_string(n->name); - if (header & impl::xml_memory_page_value_allocated_mask) alloc.deallocate_string(n->value); - - for (xml_attribute_struct* attr = n->first_attribute; attr; ) - { - xml_attribute_struct* next = attr->next_attribute; - - destroy_attribute(attr, alloc); - - attr = next; - } - - for (xml_node_struct* child = n->first_child; child; ) - { - xml_node_struct* next = child->next_sibling; - - destroy_node(child, alloc); - - child = next; - } - - alloc.deallocate_memory(n, sizeof(xml_node_struct), reinterpret_cast(header & xml_memory_page_pointer_mask)); - } - - PUGI__FN_NO_INLINE xml_node_struct* append_node(xml_node_struct* node, xml_allocator& alloc, xml_node_type type = node_element) - { - xml_node_struct* child = allocate_node(alloc, type); - if (!child) return 0; - - child->parent = node; - - xml_node_struct* first_child = node->first_child; - - if (first_child) - { - xml_node_struct* last_child = first_child->prev_sibling_c; - - last_child->next_sibling = child; - child->prev_sibling_c = last_child; - first_child->prev_sibling_c = child; - } - else - { - node->first_child = child; - child->prev_sibling_c = child; - } - - return child; - } - - PUGI__FN_NO_INLINE xml_attribute_struct* append_attribute_ll(xml_node_struct* node, xml_allocator& alloc) - { - xml_attribute_struct* a = allocate_attribute(alloc); - if (!a) return 0; - - xml_attribute_struct* first_attribute = node->first_attribute; - - if (first_attribute) - { - xml_attribute_struct* last_attribute = first_attribute->prev_attribute_c; - - last_attribute->next_attribute = a; - a->prev_attribute_c = last_attribute; - first_attribute->prev_attribute_c = a; - } - else - { - node->first_attribute = a; - a->prev_attribute_c = a; - } - - return a; - } -PUGI__NS_END - -// Helper classes for code generation -PUGI__NS_BEGIN - struct opt_false - { - enum { value = 0 }; - }; - - struct opt_true - { - enum { value = 1 }; - }; -PUGI__NS_END - -// Unicode utilities -PUGI__NS_BEGIN - inline uint16_t endian_swap(uint16_t value) - { - return static_cast(((value & 0xff) << 8) | (value >> 8)); - } - - inline uint32_t endian_swap(uint32_t value) - { - return ((value & 0xff) << 24) | ((value & 0xff00) << 8) | ((value & 0xff0000) >> 8) | (value >> 24); - } - - struct utf8_counter - { - typedef size_t value_type; - - static value_type low(value_type result, uint32_t ch) - { - // U+0000..U+007F - if (ch < 0x80) return result + 1; - // U+0080..U+07FF - else if (ch < 0x800) return result + 2; - // U+0800..U+FFFF - else return result + 3; - } - - static value_type high(value_type result, uint32_t) - { - // U+10000..U+10FFFF - return result + 4; - } - }; - - struct utf8_writer - { - typedef uint8_t* value_type; - - static value_type low(value_type result, uint32_t ch) - { - // U+0000..U+007F - if (ch < 0x80) - { - *result = static_cast(ch); - return result + 1; - } - // U+0080..U+07FF - else if (ch < 0x800) - { - result[0] = static_cast(0xC0 | (ch >> 6)); - result[1] = static_cast(0x80 | (ch & 0x3F)); - return result + 2; - } - // U+0800..U+FFFF - else - { - result[0] = static_cast(0xE0 | (ch >> 12)); - result[1] = static_cast(0x80 | ((ch >> 6) & 0x3F)); - result[2] = static_cast(0x80 | (ch & 0x3F)); - return result + 3; - } - } - - static value_type high(value_type result, uint32_t ch) - { - // U+10000..U+10FFFF - result[0] = static_cast(0xF0 | (ch >> 18)); - result[1] = static_cast(0x80 | ((ch >> 12) & 0x3F)); - result[2] = static_cast(0x80 | ((ch >> 6) & 0x3F)); - result[3] = static_cast(0x80 | (ch & 0x3F)); - return result + 4; - } - - static value_type any(value_type result, uint32_t ch) - { - return (ch < 0x10000) ? low(result, ch) : high(result, ch); - } - }; - - struct utf16_counter - { - typedef size_t value_type; - - static value_type low(value_type result, uint32_t) - { - return result + 1; - } - - static value_type high(value_type result, uint32_t) - { - return result + 2; - } - }; - - struct utf16_writer - { - typedef uint16_t* value_type; - - static value_type low(value_type result, uint32_t ch) - { - *result = static_cast(ch); - - return result + 1; - } - - static value_type high(value_type result, uint32_t ch) - { - uint32_t msh = static_cast(ch - 0x10000) >> 10; - uint32_t lsh = static_cast(ch - 0x10000) & 0x3ff; - - result[0] = static_cast(0xD800 + msh); - result[1] = static_cast(0xDC00 + lsh); - - return result + 2; - } - - static value_type any(value_type result, uint32_t ch) - { - return (ch < 0x10000) ? low(result, ch) : high(result, ch); - } - }; - - struct utf32_counter - { - typedef size_t value_type; - - static value_type low(value_type result, uint32_t) - { - return result + 1; - } - - static value_type high(value_type result, uint32_t) - { - return result + 1; - } - }; - - struct utf32_writer - { - typedef uint32_t* value_type; - - static value_type low(value_type result, uint32_t ch) - { - *result = ch; - - return result + 1; - } - - static value_type high(value_type result, uint32_t ch) - { - *result = ch; - - return result + 1; - } - - static value_type any(value_type result, uint32_t ch) - { - *result = ch; - - return result + 1; - } - }; - - struct latin1_writer - { - typedef uint8_t* value_type; - - static value_type low(value_type result, uint32_t ch) - { - *result = static_cast(ch > 255 ? '?' : ch); - - return result + 1; - } - - static value_type high(value_type result, uint32_t ch) - { - (void)ch; - - *result = '?'; - - return result + 1; - } - }; - - template struct wchar_selector; - - template <> struct wchar_selector<2> - { - typedef uint16_t type; - typedef utf16_counter counter; - typedef utf16_writer writer; - }; - - template <> struct wchar_selector<4> - { - typedef uint32_t type; - typedef utf32_counter counter; - typedef utf32_writer writer; - }; - - typedef wchar_selector::counter wchar_counter; - typedef wchar_selector::writer wchar_writer; - - template struct utf_decoder - { - static inline typename Traits::value_type decode_utf8_block(const uint8_t* data, size_t size, typename Traits::value_type result) - { - const uint8_t utf8_byte_mask = 0x3f; - - while (size) - { - uint8_t lead = *data; - - // 0xxxxxxx -> U+0000..U+007F - if (lead < 0x80) - { - result = Traits::low(result, lead); - data += 1; - size -= 1; - - // process aligned single-byte (ascii) blocks - if ((reinterpret_cast(data) & 3) == 0) - { - // round-trip through void* to silence 'cast increases required alignment of target type' warnings - while (size >= 4 && (*static_cast(static_cast(data)) & 0x80808080) == 0) - { - result = Traits::low(result, data[0]); - result = Traits::low(result, data[1]); - result = Traits::low(result, data[2]); - result = Traits::low(result, data[3]); - data += 4; - size -= 4; - } - } - } - // 110xxxxx -> U+0080..U+07FF - else if (static_cast(lead - 0xC0) < 0x20 && size >= 2 && (data[1] & 0xc0) == 0x80) - { - result = Traits::low(result, ((lead & ~0xC0) << 6) | (data[1] & utf8_byte_mask)); - data += 2; - size -= 2; - } - // 1110xxxx -> U+0800-U+FFFF - else if (static_cast(lead - 0xE0) < 0x10 && size >= 3 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80) - { - result = Traits::low(result, ((lead & ~0xE0) << 12) | ((data[1] & utf8_byte_mask) << 6) | (data[2] & utf8_byte_mask)); - data += 3; - size -= 3; - } - // 11110xxx -> U+10000..U+10FFFF - else if (static_cast(lead - 0xF0) < 0x08 && size >= 4 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80 && (data[3] & 0xc0) == 0x80) - { - result = Traits::high(result, ((lead & ~0xF0) << 18) | ((data[1] & utf8_byte_mask) << 12) | ((data[2] & utf8_byte_mask) << 6) | (data[3] & utf8_byte_mask)); - data += 4; - size -= 4; - } - // 10xxxxxx or 11111xxx -> invalid - else - { - data += 1; - size -= 1; - } - } - - return result; - } - - static inline typename Traits::value_type decode_utf16_block(const uint16_t* data, size_t size, typename Traits::value_type result) - { - const uint16_t* end = data + size; - - while (data < end) - { - uint16_t lead = opt_swap::value ? endian_swap(*data) : *data; - - // U+0000..U+D7FF - if (lead < 0xD800) - { - result = Traits::low(result, lead); - data += 1; - } - // U+E000..U+FFFF - else if (static_cast(lead - 0xE000) < 0x2000) - { - result = Traits::low(result, lead); - data += 1; - } - // surrogate pair lead - else if (static_cast(lead - 0xD800) < 0x400 && data + 1 < end) - { - uint16_t next = opt_swap::value ? endian_swap(data[1]) : data[1]; - - if (static_cast(next - 0xDC00) < 0x400) - { - result = Traits::high(result, 0x10000 + ((lead & 0x3ff) << 10) + (next & 0x3ff)); - data += 2; - } - else - { - data += 1; - } - } - else - { - data += 1; - } - } - - return result; - } - - static inline typename Traits::value_type decode_utf32_block(const uint32_t* data, size_t size, typename Traits::value_type result) - { - const uint32_t* end = data + size; - - while (data < end) - { - uint32_t lead = opt_swap::value ? endian_swap(*data) : *data; - - // U+0000..U+FFFF - if (lead < 0x10000) - { - result = Traits::low(result, lead); - data += 1; - } - // U+10000..U+10FFFF - else - { - result = Traits::high(result, lead); - data += 1; - } - } - - return result; - } - - static inline typename Traits::value_type decode_latin1_block(const uint8_t* data, size_t size, typename Traits::value_type result) - { - for (size_t i = 0; i < size; ++i) - { - result = Traits::low(result, data[i]); - } - - return result; - } - - static inline typename Traits::value_type decode_wchar_block_impl(const uint16_t* data, size_t size, typename Traits::value_type result) - { - return decode_utf16_block(data, size, result); - } - - static inline typename Traits::value_type decode_wchar_block_impl(const uint32_t* data, size_t size, typename Traits::value_type result) - { - return decode_utf32_block(data, size, result); - } - - static inline typename Traits::value_type decode_wchar_block(const wchar_t* data, size_t size, typename Traits::value_type result) - { - return decode_wchar_block_impl(reinterpret_cast::type*>(data), size, result); - } - }; - - template PUGI__FN void convert_utf_endian_swap(T* result, const T* data, size_t length) - { - for (size_t i = 0; i < length; ++i) result[i] = endian_swap(data[i]); - } - -#ifdef PUGIXML_WCHAR_MODE - PUGI__FN void convert_wchar_endian_swap(wchar_t* result, const wchar_t* data, size_t length) - { - for (size_t i = 0; i < length; ++i) result[i] = static_cast(endian_swap(static_cast::type>(data[i]))); - } -#endif -PUGI__NS_END - -PUGI__NS_BEGIN - enum chartype_t - { - ct_parse_pcdata = 1, // \0, &, \r, < - ct_parse_attr = 2, // \0, &, \r, ', " - ct_parse_attr_ws = 4, // \0, &, \r, ', ", \n, tab - ct_space = 8, // \r, \n, space, tab - ct_parse_cdata = 16, // \0, ], >, \r - ct_parse_comment = 32, // \0, -, >, \r - ct_symbol = 64, // Any symbol > 127, a-z, A-Z, 0-9, _, :, -, . - ct_start_symbol = 128 // Any symbol > 127, a-z, A-Z, _, : - }; - - static const unsigned char chartype_table[256] = - { - 55, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 63, 0, 0, // 0-15 - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 - 8, 0, 6, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 96, 64, 0, // 32-47 - 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 192, 0, 1, 0, 48, 0, // 48-63 - 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 64-79 - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 16, 0, 192, // 80-95 - 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 96-111 - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 0, 0, 0, // 112-127 - - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 128+ - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192 - }; - - enum chartypex_t - { - ctx_special_pcdata = 1, // Any symbol >= 0 and < 32 (except \t, \r, \n), &, <, > - ctx_special_attr = 2, // Any symbol >= 0 and < 32 (except \t), &, <, >, " - ctx_start_symbol = 4, // Any symbol > 127, a-z, A-Z, _ - ctx_digit = 8, // 0-9 - ctx_symbol = 16 // Any symbol > 127, a-z, A-Z, 0-9, _, -, . - }; - - static const unsigned char chartypex_table[256] = - { - 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 2, 3, 3, 2, 3, 3, // 0-15 - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 16-31 - 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 16, 16, 0, // 32-47 - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 3, 0, 3, 0, // 48-63 - - 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 64-79 - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 20, // 80-95 - 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 96-111 - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 0, // 112-127 - - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 128+ - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 - }; - -#ifdef PUGIXML_WCHAR_MODE - #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) ((static_cast(c) < 128 ? table[static_cast(c)] : table[128]) & (ct)) -#else - #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) (table[static_cast(c)] & (ct)) -#endif - - #define PUGI__IS_CHARTYPE(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartype_table) - #define PUGI__IS_CHARTYPEX(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartypex_table) - - PUGI__FN bool is_little_endian() - { - unsigned int ui = 1; - - return *reinterpret_cast(&ui) == 1; - } - - PUGI__FN xml_encoding get_wchar_encoding() - { - PUGI__STATIC_ASSERT(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4); - - if (sizeof(wchar_t) == 2) - return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - else - return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; - } - - PUGI__FN xml_encoding guess_buffer_encoding(uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3) - { - // look for BOM in first few bytes - if (d0 == 0 && d1 == 0 && d2 == 0xfe && d3 == 0xff) return encoding_utf32_be; - if (d0 == 0xff && d1 == 0xfe && d2 == 0 && d3 == 0) return encoding_utf32_le; - if (d0 == 0xfe && d1 == 0xff) return encoding_utf16_be; - if (d0 == 0xff && d1 == 0xfe) return encoding_utf16_le; - if (d0 == 0xef && d1 == 0xbb && d2 == 0xbf) return encoding_utf8; - - // look for <, (contents); - - PUGI__DMC_VOLATILE uint8_t d0 = data[0], d1 = data[1], d2 = data[2], d3 = data[3]; - - return guess_buffer_encoding(d0, d1, d2, d3); - } - - PUGI__FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) - { - if (is_mutable) - { - out_buffer = static_cast(const_cast(contents)); - } - else - { - void* buffer = xml_memory::allocate(size > 0 ? size : 1); - if (!buffer) return false; - - memcpy(buffer, contents, size); - - out_buffer = static_cast(buffer); - } - - out_length = size / sizeof(char_t); - - return true; - } - -#ifdef PUGIXML_WCHAR_MODE - PUGI__FN bool need_endian_swap_utf(xml_encoding le, xml_encoding re) - { - return (le == encoding_utf16_be && re == encoding_utf16_le) || (le == encoding_utf16_le && re == encoding_utf16_be) || - (le == encoding_utf32_be && re == encoding_utf32_le) || (le == encoding_utf32_le && re == encoding_utf32_be); - } - - PUGI__FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) - { - const char_t* data = static_cast(contents); - - if (is_mutable) - { - out_buffer = const_cast(data); - } - else - { - out_buffer = static_cast(xml_memory::allocate(size > 0 ? size : 1)); - if (!out_buffer) return false; - } - - out_length = size / sizeof(char_t); - - convert_wchar_endian_swap(out_buffer, data, out_length); - - return true; - } - - PUGI__FN bool convert_buffer_utf8(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size) - { - const uint8_t* data = static_cast(contents); - - // first pass: get length in wchar_t units - out_length = utf_decoder::decode_utf8_block(data, size, 0); - - // allocate buffer of suitable length - out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); - if (!out_buffer) return false; - - // second pass: convert utf8 input to wchar_t - wchar_writer::value_type out_begin = reinterpret_cast(out_buffer); - wchar_writer::value_type out_end = utf_decoder::decode_utf8_block(data, size, out_begin); - - assert(out_end == out_begin + out_length); - (void)!out_end; - - return true; - } - - template PUGI__FN bool convert_buffer_utf16(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) - { - const uint16_t* data = static_cast(contents); - size_t length = size / sizeof(uint16_t); - - // first pass: get length in wchar_t units - out_length = utf_decoder::decode_utf16_block(data, length, 0); - - // allocate buffer of suitable length - out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); - if (!out_buffer) return false; - - // second pass: convert utf16 input to wchar_t - wchar_writer::value_type out_begin = reinterpret_cast(out_buffer); - wchar_writer::value_type out_end = utf_decoder::decode_utf16_block(data, length, out_begin); - - assert(out_end == out_begin + out_length); - (void)!out_end; - - return true; - } - - template PUGI__FN bool convert_buffer_utf32(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) - { - const uint32_t* data = static_cast(contents); - size_t length = size / sizeof(uint32_t); - - // first pass: get length in wchar_t units - out_length = utf_decoder::decode_utf32_block(data, length, 0); - - // allocate buffer of suitable length - out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); - if (!out_buffer) return false; - - // second pass: convert utf32 input to wchar_t - wchar_writer::value_type out_begin = reinterpret_cast(out_buffer); - wchar_writer::value_type out_end = utf_decoder::decode_utf32_block(data, length, out_begin); - - assert(out_end == out_begin + out_length); - (void)!out_end; - - return true; - } - - PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size) - { - const uint8_t* data = static_cast(contents); - - // get length in wchar_t units - out_length = size; - - // allocate buffer of suitable length - out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); - if (!out_buffer) return false; - - // convert latin1 input to wchar_t - wchar_writer::value_type out_begin = reinterpret_cast(out_buffer); - wchar_writer::value_type out_end = utf_decoder::decode_latin1_block(data, size, out_begin); - - assert(out_end == out_begin + out_length); - (void)!out_end; - - return true; - } - - PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) - { - // get native encoding - xml_encoding wchar_encoding = get_wchar_encoding(); - - // fast path: no conversion required - if (encoding == wchar_encoding) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); - - // only endian-swapping is required - if (need_endian_swap_utf(encoding, wchar_encoding)) return convert_buffer_endian_swap(out_buffer, out_length, contents, size, is_mutable); - - // source encoding is utf8 - if (encoding == encoding_utf8) return convert_buffer_utf8(out_buffer, out_length, contents, size); - - // source encoding is utf16 - if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - - return (native_encoding == encoding) ? - convert_buffer_utf16(out_buffer, out_length, contents, size, opt_false()) : - convert_buffer_utf16(out_buffer, out_length, contents, size, opt_true()); - } - - // source encoding is utf32 - if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; - - return (native_encoding == encoding) ? - convert_buffer_utf32(out_buffer, out_length, contents, size, opt_false()) : - convert_buffer_utf32(out_buffer, out_length, contents, size, opt_true()); - } - - // source encoding is latin1 - if (encoding == encoding_latin1) return convert_buffer_latin1(out_buffer, out_length, contents, size); - - assert(!"Invalid encoding"); - return false; - } -#else - template PUGI__FN bool convert_buffer_utf16(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) - { - const uint16_t* data = static_cast(contents); - size_t length = size / sizeof(uint16_t); - - // first pass: get length in utf8 units - out_length = utf_decoder::decode_utf16_block(data, length, 0); - - // allocate buffer of suitable length - out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); - if (!out_buffer) return false; - - // second pass: convert utf16 input to utf8 - uint8_t* out_begin = reinterpret_cast(out_buffer); - uint8_t* out_end = utf_decoder::decode_utf16_block(data, length, out_begin); - - assert(out_end == out_begin + out_length); - (void)!out_end; - - return true; - } - - template PUGI__FN bool convert_buffer_utf32(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) - { - const uint32_t* data = static_cast(contents); - size_t length = size / sizeof(uint32_t); - - // first pass: get length in utf8 units - out_length = utf_decoder::decode_utf32_block(data, length, 0); - - // allocate buffer of suitable length - out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); - if (!out_buffer) return false; - - // second pass: convert utf32 input to utf8 - uint8_t* out_begin = reinterpret_cast(out_buffer); - uint8_t* out_end = utf_decoder::decode_utf32_block(data, length, out_begin); - - assert(out_end == out_begin + out_length); - (void)!out_end; - - return true; - } - - PUGI__FN size_t get_latin1_7bit_prefix_length(const uint8_t* data, size_t size) - { - for (size_t i = 0; i < size; ++i) - if (data[i] > 127) - return i; - - return size; - } - - PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) - { - const uint8_t* data = static_cast(contents); - - // get size of prefix that does not need utf8 conversion - size_t prefix_length = get_latin1_7bit_prefix_length(data, size); - assert(prefix_length <= size); - - const uint8_t* postfix = data + prefix_length; - size_t postfix_length = size - prefix_length; - - // if no conversion is needed, just return the original buffer - if (postfix_length == 0) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); - - // first pass: get length in utf8 units - out_length = prefix_length + utf_decoder::decode_latin1_block(postfix, postfix_length, 0); - - // allocate buffer of suitable length - out_buffer = static_cast(xml_memory::allocate((out_length > 0 ? out_length : 1) * sizeof(char_t))); - if (!out_buffer) return false; - - // second pass: convert latin1 input to utf8 - memcpy(out_buffer, data, prefix_length); - - uint8_t* out_begin = reinterpret_cast(out_buffer); - uint8_t* out_end = utf_decoder::decode_latin1_block(postfix, postfix_length, out_begin + prefix_length); - - assert(out_end == out_begin + out_length); - (void)!out_end; - - return true; - } - - PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) - { - // fast path: no conversion required - if (encoding == encoding_utf8) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); - - // source encoding is utf16 - if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - - return (native_encoding == encoding) ? - convert_buffer_utf16(out_buffer, out_length, contents, size, opt_false()) : - convert_buffer_utf16(out_buffer, out_length, contents, size, opt_true()); - } - - // source encoding is utf32 - if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) - { - xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; - - return (native_encoding == encoding) ? - convert_buffer_utf32(out_buffer, out_length, contents, size, opt_false()) : - convert_buffer_utf32(out_buffer, out_length, contents, size, opt_true()); - } - - // source encoding is latin1 - if (encoding == encoding_latin1) return convert_buffer_latin1(out_buffer, out_length, contents, size, is_mutable); - - assert(!"Invalid encoding"); - return false; - } -#endif - - PUGI__FN size_t as_utf8_begin(const wchar_t* str, size_t length) - { - // get length in utf8 characters - return utf_decoder::decode_wchar_block(str, length, 0); - } - - PUGI__FN void as_utf8_end(char* buffer, size_t size, const wchar_t* str, size_t length) - { - // convert to utf8 - uint8_t* begin = reinterpret_cast(buffer); - uint8_t* end = utf_decoder::decode_wchar_block(str, length, begin); - - assert(begin + size == end); - (void)!end; - - // zero-terminate - buffer[size] = 0; - } - -#ifndef PUGIXML_NO_STL - PUGI__FN std::string as_utf8_impl(const wchar_t* str, size_t length) - { - // first pass: get length in utf8 characters - size_t size = as_utf8_begin(str, length); - - // allocate resulting string - std::string result; - result.resize(size); - - // second pass: convert to utf8 - if (size > 0) as_utf8_end(&result[0], size, str, length); - - return result; - } - - PUGI__FN std::basic_string as_wide_impl(const char* str, size_t size) - { - const uint8_t* data = reinterpret_cast(str); - - // first pass: get length in wchar_t units - size_t length = utf_decoder::decode_utf8_block(data, size, 0); - - // allocate resulting string - std::basic_string result; - result.resize(length); - - // second pass: convert to wchar_t - if (length > 0) - { - wchar_writer::value_type begin = reinterpret_cast(&result[0]); - wchar_writer::value_type end = utf_decoder::decode_utf8_block(data, size, begin); - - assert(begin + length == end); - (void)!end; - } - - return result; - } -#endif - - inline bool strcpy_insitu_allow(size_t length, uintptr_t allocated, char_t* target) - { - assert(target); - size_t target_length = strlength(target); - - // always reuse document buffer memory if possible - if (!allocated) return target_length >= length; - - // reuse heap memory if waste is not too great - const size_t reuse_threshold = 32; - - return target_length >= length && (target_length < reuse_threshold || target_length - length < target_length / 2); - } - - PUGI__FN bool strcpy_insitu(char_t*& dest, uintptr_t& header, uintptr_t header_mask, const char_t* source) - { - size_t source_length = strlength(source); - - if (source_length == 0) - { - // empty string and null pointer are equivalent, so just deallocate old memory - xml_allocator* alloc = reinterpret_cast(header & xml_memory_page_pointer_mask)->allocator; - - if (header & header_mask) alloc->deallocate_string(dest); - - // mark the string as not allocated - dest = 0; - header &= ~header_mask; - - return true; - } - else if (dest && strcpy_insitu_allow(source_length, header & header_mask, dest)) - { - // we can reuse old buffer, so just copy the new data (including zero terminator) - memcpy(dest, source, (source_length + 1) * sizeof(char_t)); - - return true; - } - else - { - xml_allocator* alloc = reinterpret_cast(header & xml_memory_page_pointer_mask)->allocator; - - // allocate new buffer - char_t* buf = alloc->allocate_string(source_length + 1); - if (!buf) return false; - - // copy the string (including zero terminator) - memcpy(buf, source, (source_length + 1) * sizeof(char_t)); - - // deallocate old buffer (*after* the above to protect against overlapping memory and/or allocation failures) - if (header & header_mask) alloc->deallocate_string(dest); - - // the string is now allocated, so set the flag - dest = buf; - header |= header_mask; - - return true; - } - } - - struct gap - { - char_t* end; - size_t size; - - gap(): end(0), size(0) - { - } - - // Push new gap, move s count bytes further (skipping the gap). - // Collapse previous gap. - void push(char_t*& s, size_t count) - { - if (end) // there was a gap already; collapse it - { - // Move [old_gap_end, new_gap_start) to [old_gap_start, ...) - assert(s >= end); - memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); - } - - s += count; // end of current gap - - // "merge" two gaps - end = s; - size += count; - } - - // Collapse all gaps, return past-the-end pointer - char_t* flush(char_t* s) - { - if (end) - { - // Move [old_gap_end, current_pos) to [old_gap_start, ...) - assert(s >= end); - memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); - - return s - size; - } - else return s; - } - }; - - PUGI__FN char_t* strconv_escape(char_t* s, gap& g) - { - char_t* stre = s + 1; - - switch (*stre) - { - case '#': // &#... - { - unsigned int ucsc = 0; - - if (stre[1] == 'x') // &#x... (hex code) - { - stre += 2; - - char_t ch = *stre; - - if (ch == ';') return stre; - - for (;;) - { - if (static_cast(ch - '0') <= 9) - ucsc = 16 * ucsc + (ch - '0'); - else if (static_cast((ch | ' ') - 'a') <= 5) - ucsc = 16 * ucsc + ((ch | ' ') - 'a' + 10); - else if (ch == ';') - break; - else // cancel - return stre; - - ch = *++stre; - } - - ++stre; - } - else // &#... (dec code) - { - char_t ch = *++stre; - - if (ch == ';') return stre; - - for (;;) - { - if (static_cast(ch - '0') <= 9) - ucsc = 10 * ucsc + (ch - '0'); - else if (ch == ';') - break; - else // cancel - return stre; - - ch = *++stre; - } - - ++stre; - } - - #ifdef PUGIXML_WCHAR_MODE - s = reinterpret_cast(wchar_writer::any(reinterpret_cast(s), ucsc)); - #else - s = reinterpret_cast(utf8_writer::any(reinterpret_cast(s), ucsc)); - #endif - - g.push(s, stre - s); - return stre; - } - - case 'a': // &a - { - ++stre; - - if (*stre == 'm') // &am - { - if (*++stre == 'p' && *++stre == ';') // & - { - *s++ = '&'; - ++stre; - - g.push(s, stre - s); - return stre; - } - } - else if (*stre == 'p') // &ap - { - if (*++stre == 'o' && *++stre == 's' && *++stre == ';') // ' - { - *s++ = '\''; - ++stre; - - g.push(s, stre - s); - return stre; - } - } - break; - } - - case 'g': // &g - { - if (*++stre == 't' && *++stre == ';') // > - { - *s++ = '>'; - ++stre; - - g.push(s, stre - s); - return stre; - } - break; - } - - case 'l': // &l - { - if (*++stre == 't' && *++stre == ';') // < - { - *s++ = '<'; - ++stre; - - g.push(s, stre - s); - return stre; - } - break; - } - - case 'q': // &q - { - if (*++stre == 'u' && *++stre == 'o' && *++stre == 't' && *++stre == ';') // " - { - *s++ = '"'; - ++stre; - - g.push(s, stre - s); - return stre; - } - break; - } - - default: - break; - } - - return stre; - } - - // Utility macro for last character handling - #define ENDSWITH(c, e) ((c) == (e) || ((c) == 0 && endch == (e))) - - PUGI__FN char_t* strconv_comment(char_t* s, char_t endch) - { - gap g; - - while (true) - { - while (!PUGI__IS_CHARTYPE(*s, ct_parse_comment)) ++s; - - if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair - { - *s++ = '\n'; // replace first one with 0x0a - - if (*s == '\n') g.push(s, 1); - } - else if (s[0] == '-' && s[1] == '-' && ENDSWITH(s[2], '>')) // comment ends here - { - *g.flush(s) = 0; - - return s + (s[2] == '>' ? 3 : 2); - } - else if (*s == 0) - { - return 0; - } - else ++s; - } - } - - PUGI__FN char_t* strconv_cdata(char_t* s, char_t endch) - { - gap g; - - while (true) - { - while (!PUGI__IS_CHARTYPE(*s, ct_parse_cdata)) ++s; - - if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair - { - *s++ = '\n'; // replace first one with 0x0a - - if (*s == '\n') g.push(s, 1); - } - else if (s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>')) // CDATA ends here - { - *g.flush(s) = 0; - - return s + 1; - } - else if (*s == 0) - { - return 0; - } - else ++s; - } - } - - typedef char_t* (*strconv_pcdata_t)(char_t*); - - template struct strconv_pcdata_impl - { - static char_t* parse(char_t* s) - { - gap g; - - while (true) - { - while (!PUGI__IS_CHARTYPE(*s, ct_parse_pcdata)) ++s; - - if (*s == '<') // PCDATA ends here - { - *g.flush(s) = 0; - - return s + 1; - } - else if (opt_eol::value && *s == '\r') // Either a single 0x0d or 0x0d 0x0a pair - { - *s++ = '\n'; // replace first one with 0x0a - - if (*s == '\n') g.push(s, 1); - } - else if (opt_escape::value && *s == '&') - { - s = strconv_escape(s, g); - } - else if (*s == 0) - { - return s; - } - else ++s; - } - } - }; - - PUGI__FN strconv_pcdata_t get_strconv_pcdata(unsigned int optmask) - { - PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20); - - switch ((optmask >> 4) & 3) // get bitmask for flags (eol escapes) - { - case 0: return strconv_pcdata_impl::parse; - case 1: return strconv_pcdata_impl::parse; - case 2: return strconv_pcdata_impl::parse; - case 3: return strconv_pcdata_impl::parse; - default: return 0; // should not get here - } - } - - typedef char_t* (*strconv_attribute_t)(char_t*, char_t); - - template struct strconv_attribute_impl - { - static char_t* parse_wnorm(char_t* s, char_t end_quote) - { - gap g; - - // trim leading whitespaces - if (PUGI__IS_CHARTYPE(*s, ct_space)) - { - char_t* str = s; - - do ++str; - while (PUGI__IS_CHARTYPE(*str, ct_space)); - - g.push(s, str - s); - } - - while (true) - { - while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr_ws | ct_space)) ++s; - - if (*s == end_quote) - { - char_t* str = g.flush(s); - - do *str-- = 0; - while (PUGI__IS_CHARTYPE(*str, ct_space)); - - return s + 1; - } - else if (PUGI__IS_CHARTYPE(*s, ct_space)) - { - *s++ = ' '; - - if (PUGI__IS_CHARTYPE(*s, ct_space)) - { - char_t* str = s + 1; - while (PUGI__IS_CHARTYPE(*str, ct_space)) ++str; - - g.push(s, str - s); - } - } - else if (opt_escape::value && *s == '&') - { - s = strconv_escape(s, g); - } - else if (!*s) - { - return 0; - } - else ++s; - } - } - - static char_t* parse_wconv(char_t* s, char_t end_quote) - { - gap g; - - while (true) - { - while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr_ws)) ++s; - - if (*s == end_quote) - { - *g.flush(s) = 0; - - return s + 1; - } - else if (PUGI__IS_CHARTYPE(*s, ct_space)) - { - if (*s == '\r') - { - *s++ = ' '; - - if (*s == '\n') g.push(s, 1); - } - else *s++ = ' '; - } - else if (opt_escape::value && *s == '&') - { - s = strconv_escape(s, g); - } - else if (!*s) - { - return 0; - } - else ++s; - } - } - - static char_t* parse_eol(char_t* s, char_t end_quote) - { - gap g; - - while (true) - { - while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr)) ++s; - - if (*s == end_quote) - { - *g.flush(s) = 0; - - return s + 1; - } - else if (*s == '\r') - { - *s++ = '\n'; - - if (*s == '\n') g.push(s, 1); - } - else if (opt_escape::value && *s == '&') - { - s = strconv_escape(s, g); - } - else if (!*s) - { - return 0; - } - else ++s; - } - } - - static char_t* parse_simple(char_t* s, char_t end_quote) - { - gap g; - - while (true) - { - while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr)) ++s; - - if (*s == end_quote) - { - *g.flush(s) = 0; - - return s + 1; - } - else if (opt_escape::value && *s == '&') - { - s = strconv_escape(s, g); - } - else if (!*s) - { - return 0; - } - else ++s; - } - } - }; - - PUGI__FN strconv_attribute_t get_strconv_attribute(unsigned int optmask) - { - PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_wconv_attribute == 0x40 && parse_wnorm_attribute == 0x80); - - switch ((optmask >> 4) & 15) // get bitmask for flags (wconv wnorm eol escapes) - { - case 0: return strconv_attribute_impl::parse_simple; - case 1: return strconv_attribute_impl::parse_simple; - case 2: return strconv_attribute_impl::parse_eol; - case 3: return strconv_attribute_impl::parse_eol; - case 4: return strconv_attribute_impl::parse_wconv; - case 5: return strconv_attribute_impl::parse_wconv; - case 6: return strconv_attribute_impl::parse_wconv; - case 7: return strconv_attribute_impl::parse_wconv; - case 8: return strconv_attribute_impl::parse_wnorm; - case 9: return strconv_attribute_impl::parse_wnorm; - case 10: return strconv_attribute_impl::parse_wnorm; - case 11: return strconv_attribute_impl::parse_wnorm; - case 12: return strconv_attribute_impl::parse_wnorm; - case 13: return strconv_attribute_impl::parse_wnorm; - case 14: return strconv_attribute_impl::parse_wnorm; - case 15: return strconv_attribute_impl::parse_wnorm; - default: return 0; // should not get here - } - } - - inline xml_parse_result make_parse_result(xml_parse_status status, ptrdiff_t offset = 0) - { - xml_parse_result result; - result.status = status; - result.offset = offset; - - return result; - } - - struct xml_parser - { - xml_allocator alloc; - char_t* error_offset; - xml_parse_status error_status; - - // Parser utilities. - #define PUGI__SKIPWS() { while (PUGI__IS_CHARTYPE(*s, ct_space)) ++s; } - #define PUGI__OPTSET(OPT) ( optmsk & (OPT) ) - #define PUGI__PUSHNODE(TYPE) { cursor = append_node(cursor, alloc, TYPE); if (!cursor) PUGI__THROW_ERROR(status_out_of_memory, s); } - #define PUGI__POPNODE() { cursor = cursor->parent; } - #define PUGI__SCANFOR(X) { while (*s != 0 && !(X)) ++s; } - #define PUGI__SCANWHILE(X) { while ((X)) ++s; } - #define PUGI__ENDSEG() { ch = *s; *s = 0; ++s; } - #define PUGI__THROW_ERROR(err, m) return error_offset = m, error_status = err, static_cast(0) - #define PUGI__CHECK_ERROR(err, m) { if (*s == 0) PUGI__THROW_ERROR(err, m); } - - xml_parser(const xml_allocator& alloc_): alloc(alloc_), error_offset(0), error_status(status_ok) - { - } - - // DOCTYPE consists of nested sections of the following possible types: - // , , "...", '...' - // - // - // First group can not contain nested groups - // Second group can contain nested groups of the same type - // Third group can contain all other groups - char_t* parse_doctype_primitive(char_t* s) - { - if (*s == '"' || *s == '\'') - { - // quoted string - char_t ch = *s++; - PUGI__SCANFOR(*s == ch); - if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); - - s++; - } - else if (s[0] == '<' && s[1] == '?') - { - // - s += 2; - PUGI__SCANFOR(s[0] == '?' && s[1] == '>'); // no need for ENDSWITH because ?> can't terminate proper doctype - if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); - - s += 2; - } - else if (s[0] == '<' && s[1] == '!' && s[2] == '-' && s[3] == '-') - { - s += 4; - PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && s[2] == '>'); // no need for ENDSWITH because --> can't terminate proper doctype - if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); - - s += 4; - } - else PUGI__THROW_ERROR(status_bad_doctype, s); - - return s; - } - - char_t* parse_doctype_ignore(char_t* s) - { - assert(s[0] == '<' && s[1] == '!' && s[2] == '['); - s++; - - while (*s) - { - if (s[0] == '<' && s[1] == '!' && s[2] == '[') - { - // nested ignore section - s = parse_doctype_ignore(s); - if (!s) return s; - } - else if (s[0] == ']' && s[1] == ']' && s[2] == '>') - { - // ignore section end - s += 3; - - return s; - } - else s++; - } - - PUGI__THROW_ERROR(status_bad_doctype, s); - } - - char_t* parse_doctype_group(char_t* s, char_t endch, bool toplevel) - { - assert(s[0] == '<' && s[1] == '!'); - s++; - - while (*s) - { - if (s[0] == '<' && s[1] == '!' && s[2] != '-') - { - if (s[2] == '[') - { - // ignore - s = parse_doctype_ignore(s); - if (!s) return s; - } - else - { - // some control group - s = parse_doctype_group(s, endch, false); - if (!s) return s; - } - } - else if (s[0] == '<' || s[0] == '"' || s[0] == '\'') - { - // unknown tag (forbidden), or some primitive group - s = parse_doctype_primitive(s); - if (!s) return s; - } - else if (*s == '>') - { - s++; - - return s; - } - else s++; - } - - if (!toplevel || endch != '>') PUGI__THROW_ERROR(status_bad_doctype, s); - - return s; - } - - char_t* parse_exclamation(char_t* s, xml_node_struct* cursor, unsigned int optmsk, char_t endch) - { - // parse node contents, starting with exclamation mark - ++s; - - if (*s == '-') // 'value = s; // Save the offset. - } - - if (PUGI__OPTSET(parse_eol) && PUGI__OPTSET(parse_comments)) - { - s = strconv_comment(s, endch); - - if (!s) PUGI__THROW_ERROR(status_bad_comment, cursor->value); - } - else - { - // Scan for terminating '-->'. - PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && ENDSWITH(s[2], '>')); - PUGI__CHECK_ERROR(status_bad_comment, s); - - if (PUGI__OPTSET(parse_comments)) - *s = 0; // Zero-terminate this segment at the first terminating '-'. - - s += (s[2] == '>' ? 3 : 2); // Step over the '\0->'. - } - } - else PUGI__THROW_ERROR(status_bad_comment, s); - } - else if (*s == '[') - { - // 'value = s; // Save the offset. - - if (PUGI__OPTSET(parse_eol)) - { - s = strconv_cdata(s, endch); - - if (!s) PUGI__THROW_ERROR(status_bad_cdata, cursor->value); - } - else - { - // Scan for terminating ']]>'. - PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>')); - PUGI__CHECK_ERROR(status_bad_cdata, s); - - *s++ = 0; // Zero-terminate this segment. - } - } - else // Flagged for discard, but we still have to scan for the terminator. - { - // Scan for terminating ']]>'. - PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>')); - PUGI__CHECK_ERROR(status_bad_cdata, s); - - ++s; - } - - s += (s[1] == '>' ? 2 : 1); // Step over the last ']>'. - } - else PUGI__THROW_ERROR(status_bad_cdata, s); - } - else if (s[0] == 'D' && s[1] == 'O' && s[2] == 'C' && s[3] == 'T' && s[4] == 'Y' && s[5] == 'P' && ENDSWITH(s[6], 'E')) - { - s -= 2; - - if (cursor->parent) PUGI__THROW_ERROR(status_bad_doctype, s); - - char_t* mark = s + 9; - - s = parse_doctype_group(s, endch, true); - if (!s) return s; - - if (PUGI__OPTSET(parse_doctype)) - { - while (PUGI__IS_CHARTYPE(*mark, ct_space)) ++mark; - - PUGI__PUSHNODE(node_doctype); - - cursor->value = mark; - - assert((s[0] == 0 && endch == '>') || s[-1] == '>'); - s[*s == 0 ? 0 : -1] = 0; - - PUGI__POPNODE(); - } - } - else if (*s == 0 && endch == '-') PUGI__THROW_ERROR(status_bad_comment, s); - else if (*s == 0 && endch == '[') PUGI__THROW_ERROR(status_bad_cdata, s); - else PUGI__THROW_ERROR(status_unrecognized_tag, s); - - return s; - } - - char_t* parse_question(char_t* s, xml_node_struct*& ref_cursor, unsigned int optmsk, char_t endch) - { - // load into registers - xml_node_struct* cursor = ref_cursor; - char_t ch = 0; - - // parse node contents, starting with question mark - ++s; - - // read PI target - char_t* target = s; - - if (!PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_pi, s); - - PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); - PUGI__CHECK_ERROR(status_bad_pi, s); - - // determine node type; stricmp / strcasecmp is not portable - bool declaration = (target[0] | ' ') == 'x' && (target[1] | ' ') == 'm' && (target[2] | ' ') == 'l' && target + 3 == s; - - if (declaration ? PUGI__OPTSET(parse_declaration) : PUGI__OPTSET(parse_pi)) - { - if (declaration) - { - // disallow non top-level declarations - if (cursor->parent) PUGI__THROW_ERROR(status_bad_pi, s); - - PUGI__PUSHNODE(node_declaration); - } - else - { - PUGI__PUSHNODE(node_pi); - } - - cursor->name = target; - - PUGI__ENDSEG(); - - // parse value/attributes - if (ch == '?') - { - // empty node - if (!ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_pi, s); - s += (*s == '>'); - - PUGI__POPNODE(); - } - else if (PUGI__IS_CHARTYPE(ch, ct_space)) - { - PUGI__SKIPWS(); - - // scan for tag end - char_t* value = s; - - PUGI__SCANFOR(s[0] == '?' && ENDSWITH(s[1], '>')); - PUGI__CHECK_ERROR(status_bad_pi, s); - - if (declaration) - { - // replace ending ? with / so that 'element' terminates properly - *s = '/'; - - // we exit from this function with cursor at node_declaration, which is a signal to parse() to go to LOC_ATTRIBUTES - s = value; - } - else - { - // store value and step over > - cursor->value = value; - PUGI__POPNODE(); - - PUGI__ENDSEG(); - - s += (*s == '>'); - } - } - else PUGI__THROW_ERROR(status_bad_pi, s); - } - else - { - // scan for tag end - PUGI__SCANFOR(s[0] == '?' && ENDSWITH(s[1], '>')); - PUGI__CHECK_ERROR(status_bad_pi, s); - - s += (s[1] == '>' ? 2 : 1); - } - - // store from registers - ref_cursor = cursor; - - return s; - } - - char_t* parse(char_t* s, xml_node_struct* xmldoc, unsigned int optmsk, char_t endch) - { - strconv_attribute_t strconv_attribute = get_strconv_attribute(optmsk); - strconv_pcdata_t strconv_pcdata = get_strconv_pcdata(optmsk); - - char_t ch = 0; - xml_node_struct* cursor = xmldoc; - char_t* mark = s; - - while (*s != 0) - { - if (*s == '<') - { - ++s; - - LOC_TAG: - if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // '<#...' - { - PUGI__PUSHNODE(node_element); // Append a new node to the tree. - - cursor->name = s; - - PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); // Scan for a terminator. - PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. - - if (ch == '>') - { - // end of tag - } - else if (PUGI__IS_CHARTYPE(ch, ct_space)) - { - LOC_ATTRIBUTES: - while (true) - { - PUGI__SKIPWS(); // Eat any whitespace. - - if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // <... #... - { - xml_attribute_struct* a = append_attribute_ll(cursor, alloc); // Make space for this attribute. - if (!a) PUGI__THROW_ERROR(status_out_of_memory, s); - - a->name = s; // Save the offset. - - PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); // Scan for a terminator. - PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance - - PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. - PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance - - if (PUGI__IS_CHARTYPE(ch, ct_space)) - { - PUGI__SKIPWS(); // Eat any whitespace. - PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance - - ch = *s; - ++s; - } - - if (ch == '=') // '<... #=...' - { - PUGI__SKIPWS(); // Eat any whitespace. - - if (*s == '"' || *s == '\'') // '<... #="...' - { - ch = *s; // Save quote char to avoid breaking on "''" -or- '""'. - ++s; // Step over the quote. - a->value = s; // Save the offset. - - s = strconv_attribute(s, ch); - - if (!s) PUGI__THROW_ERROR(status_bad_attribute, a->value); - - // After this line the loop continues from the start; - // Whitespaces, / and > are ok, symbols and EOF are wrong, - // everything else will be detected - if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_attribute, s); - } - else PUGI__THROW_ERROR(status_bad_attribute, s); - } - else PUGI__THROW_ERROR(status_bad_attribute, s); - } - else if (*s == '/') - { - ++s; - - if (*s == '>') - { - PUGI__POPNODE(); - s++; - break; - } - else if (*s == 0 && endch == '>') - { - PUGI__POPNODE(); - break; - } - else PUGI__THROW_ERROR(status_bad_start_element, s); - } - else if (*s == '>') - { - ++s; - - break; - } - else if (*s == 0 && endch == '>') - { - break; - } - else PUGI__THROW_ERROR(status_bad_start_element, s); - } - - // !!! - } - else if (ch == '/') // '<#.../' - { - if (!ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_start_element, s); - - PUGI__POPNODE(); // Pop. - - s += (*s == '>'); - } - else if (ch == 0) - { - // we stepped over null terminator, backtrack & handle closing tag - --s; - - if (endch != '>') PUGI__THROW_ERROR(status_bad_start_element, s); - } - else PUGI__THROW_ERROR(status_bad_start_element, s); - } - else if (*s == '/') - { - ++s; - - char_t* name = cursor->name; - if (!name) PUGI__THROW_ERROR(status_end_element_mismatch, s); - - while (PUGI__IS_CHARTYPE(*s, ct_symbol)) - { - if (*s++ != *name++) PUGI__THROW_ERROR(status_end_element_mismatch, s); - } - - if (*name) - { - if (*s == 0 && name[0] == endch && name[1] == 0) PUGI__THROW_ERROR(status_bad_end_element, s); - else PUGI__THROW_ERROR(status_end_element_mismatch, s); - } - - PUGI__POPNODE(); // Pop. - - PUGI__SKIPWS(); - - if (*s == 0) - { - if (endch != '>') PUGI__THROW_ERROR(status_bad_end_element, s); - } - else - { - if (*s != '>') PUGI__THROW_ERROR(status_bad_end_element, s); - ++s; - } - } - else if (*s == '?') // 'header & xml_memory_page_type_mask) + 1 == node_declaration) goto LOC_ATTRIBUTES; - } - else if (*s == '!') // 'first_child) continue; - } - } - - s = mark; - - if (cursor->parent) - { - PUGI__PUSHNODE(node_pcdata); // Append a new node on the tree. - cursor->value = s; // Save the offset. - - s = strconv_pcdata(s); - - PUGI__POPNODE(); // Pop since this is a standalone. - - if (!*s) break; - } - else - { - PUGI__SCANFOR(*s == '<'); // '...<' - if (!*s) break; - - ++s; - } - - // We're after '<' - goto LOC_TAG; - } - } - - // check that last tag is closed - if (cursor != xmldoc) PUGI__THROW_ERROR(status_end_element_mismatch, s); - - return s; - } - - static xml_parse_result parse(char_t* buffer, size_t length, xml_node_struct* root, unsigned int optmsk) - { - xml_document_struct* xmldoc = static_cast(root); - - // store buffer for offset_debug - xmldoc->buffer = buffer; - - // early-out for empty documents - if (length == 0) return make_parse_result(status_ok); - - // create parser on stack - xml_parser parser(*xmldoc); - - // save last character and make buffer zero-terminated (speeds up parsing) - char_t endch = buffer[length - 1]; - buffer[length - 1] = 0; - - // perform actual parsing - parser.parse(buffer, xmldoc, optmsk, endch); - - xml_parse_result result = make_parse_result(parser.error_status, parser.error_offset ? parser.error_offset - buffer : 0); - assert(result.offset >= 0 && static_cast(result.offset) <= length); - - // update allocator state - *static_cast(xmldoc) = parser.alloc; - - // since we removed last character, we have to handle the only possible false positive - if (result && endch == '<') - { - // there's no possible well-formed document with < at the end - return make_parse_result(status_unrecognized_tag, length); - } - - return result; - } - }; - - // Output facilities - PUGI__FN xml_encoding get_write_native_encoding() - { - #ifdef PUGIXML_WCHAR_MODE - return get_wchar_encoding(); - #else - return encoding_utf8; - #endif - } - - PUGI__FN xml_encoding get_write_encoding(xml_encoding encoding) - { - // replace wchar encoding with utf implementation - if (encoding == encoding_wchar) return get_wchar_encoding(); - - // replace utf16 encoding with utf16 with specific endianness - if (encoding == encoding_utf16) return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - - // replace utf32 encoding with utf32 with specific endianness - if (encoding == encoding_utf32) return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; - - // only do autodetection if no explicit encoding is requested - if (encoding != encoding_auto) return encoding; - - // assume utf8 encoding - return encoding_utf8; - } - -#ifdef PUGIXML_WCHAR_MODE - PUGI__FN size_t get_valid_length(const char_t* data, size_t length) - { - assert(length > 0); - - // discard last character if it's the lead of a surrogate pair - return (sizeof(wchar_t) == 2 && static_cast(static_cast(data[length - 1]) - 0xD800) < 0x400) ? length - 1 : length; - } - - PUGI__FN size_t convert_buffer(char_t* r_char, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) - { - // only endian-swapping is required - if (need_endian_swap_utf(encoding, get_wchar_encoding())) - { - convert_wchar_endian_swap(r_char, data, length); - - return length * sizeof(char_t); - } - - // convert to utf8 - if (encoding == encoding_utf8) - { - uint8_t* dest = r_u8; - uint8_t* end = utf_decoder::decode_wchar_block(data, length, dest); - - return static_cast(end - dest); - } - - // convert to utf16 - if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) - { - uint16_t* dest = r_u16; - - // convert to native utf16 - uint16_t* end = utf_decoder::decode_wchar_block(data, length, dest); - - // swap if necessary - xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - - if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); - - return static_cast(end - dest) * sizeof(uint16_t); - } - - // convert to utf32 - if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) - { - uint32_t* dest = r_u32; - - // convert to native utf32 - uint32_t* end = utf_decoder::decode_wchar_block(data, length, dest); - - // swap if necessary - xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; - - if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); - - return static_cast(end - dest) * sizeof(uint32_t); - } - - // convert to latin1 - if (encoding == encoding_latin1) - { - uint8_t* dest = r_u8; - uint8_t* end = utf_decoder::decode_wchar_block(data, length, dest); - - return static_cast(end - dest); - } - - assert(!"Invalid encoding"); - return 0; - } -#else - PUGI__FN size_t get_valid_length(const char_t* data, size_t length) - { - assert(length > 4); - - for (size_t i = 1; i <= 4; ++i) - { - uint8_t ch = static_cast(data[length - i]); - - // either a standalone character or a leading one - if ((ch & 0xc0) != 0x80) return length - i; - } - - // there are four non-leading characters at the end, sequence tail is broken so might as well process the whole chunk - return length; - } - - PUGI__FN size_t convert_buffer(char_t* /* r_char */, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) - { - if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) - { - uint16_t* dest = r_u16; - - // convert to native utf16 - uint16_t* end = utf_decoder::decode_utf8_block(reinterpret_cast(data), length, dest); - - // swap if necessary - xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; - - if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); - - return static_cast(end - dest) * sizeof(uint16_t); - } - - if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) - { - uint32_t* dest = r_u32; - - // convert to native utf32 - uint32_t* end = utf_decoder::decode_utf8_block(reinterpret_cast(data), length, dest); - - // swap if necessary - xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; - - if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); - - return static_cast(end - dest) * sizeof(uint32_t); - } - - if (encoding == encoding_latin1) - { - uint8_t* dest = r_u8; - uint8_t* end = utf_decoder::decode_utf8_block(reinterpret_cast(data), length, dest); - - return static_cast(end - dest); - } - - assert(!"Invalid encoding"); - return 0; - } -#endif - - class xml_buffered_writer - { - xml_buffered_writer(const xml_buffered_writer&); - xml_buffered_writer& operator=(const xml_buffered_writer&); - - public: - xml_buffered_writer(xml_writer& writer_, xml_encoding user_encoding): writer(writer_), bufsize(0), encoding(get_write_encoding(user_encoding)) - { - PUGI__STATIC_ASSERT(bufcapacity >= 8); - } - - ~xml_buffered_writer() - { - flush(); - } - - void flush() - { - flush(buffer, bufsize); - bufsize = 0; - } - - void flush(const char_t* data, size_t size) - { - if (size == 0) return; - - // fast path, just write data - if (encoding == get_write_native_encoding()) - writer.write(data, size * sizeof(char_t)); - else - { - // convert chunk - size_t result = convert_buffer(scratch.data_char, scratch.data_u8, scratch.data_u16, scratch.data_u32, data, size, encoding); - assert(result <= sizeof(scratch)); - - // write data - writer.write(scratch.data_u8, result); - } - } - - void write(const char_t* data, size_t length) - { - if (bufsize + length > bufcapacity) - { - // flush the remaining buffer contents - flush(); - - // handle large chunks - if (length > bufcapacity) - { - if (encoding == get_write_native_encoding()) - { - // fast path, can just write data chunk - writer.write(data, length * sizeof(char_t)); - return; - } - - // need to convert in suitable chunks - while (length > bufcapacity) - { - // get chunk size by selecting such number of characters that are guaranteed to fit into scratch buffer - // and form a complete codepoint sequence (i.e. discard start of last codepoint if necessary) - size_t chunk_size = get_valid_length(data, bufcapacity); - - // convert chunk and write - flush(data, chunk_size); - - // iterate - data += chunk_size; - length -= chunk_size; - } - - // small tail is copied below - bufsize = 0; - } - } - - memcpy(buffer + bufsize, data, length * sizeof(char_t)); - bufsize += length; - } - - void write(const char_t* data) - { - write(data, strlength(data)); - } - - void write(char_t d0) - { - if (bufsize + 1 > bufcapacity) flush(); - - buffer[bufsize + 0] = d0; - bufsize += 1; - } - - void write(char_t d0, char_t d1) - { - if (bufsize + 2 > bufcapacity) flush(); - - buffer[bufsize + 0] = d0; - buffer[bufsize + 1] = d1; - bufsize += 2; - } - - void write(char_t d0, char_t d1, char_t d2) - { - if (bufsize + 3 > bufcapacity) flush(); - - buffer[bufsize + 0] = d0; - buffer[bufsize + 1] = d1; - buffer[bufsize + 2] = d2; - bufsize += 3; - } - - void write(char_t d0, char_t d1, char_t d2, char_t d3) - { - if (bufsize + 4 > bufcapacity) flush(); - - buffer[bufsize + 0] = d0; - buffer[bufsize + 1] = d1; - buffer[bufsize + 2] = d2; - buffer[bufsize + 3] = d3; - bufsize += 4; - } - - void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4) - { - if (bufsize + 5 > bufcapacity) flush(); - - buffer[bufsize + 0] = d0; - buffer[bufsize + 1] = d1; - buffer[bufsize + 2] = d2; - buffer[bufsize + 3] = d3; - buffer[bufsize + 4] = d4; - bufsize += 5; - } - - void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4, char_t d5) - { - if (bufsize + 6 > bufcapacity) flush(); - - buffer[bufsize + 0] = d0; - buffer[bufsize + 1] = d1; - buffer[bufsize + 2] = d2; - buffer[bufsize + 3] = d3; - buffer[bufsize + 4] = d4; - buffer[bufsize + 5] = d5; - bufsize += 6; - } - - // utf8 maximum expansion: x4 (-> utf32) - // utf16 maximum expansion: x2 (-> utf32) - // utf32 maximum expansion: x1 - enum - { - bufcapacitybytes = - #ifdef PUGIXML_MEMORY_OUTPUT_STACK - PUGIXML_MEMORY_OUTPUT_STACK - #else - 10240 - #endif - , - bufcapacity = bufcapacitybytes / (sizeof(char_t) + 4) - }; - - char_t buffer[bufcapacity]; - - union - { - uint8_t data_u8[4 * bufcapacity]; - uint16_t data_u16[2 * bufcapacity]; - uint32_t data_u32[bufcapacity]; - char_t data_char[bufcapacity]; - } scratch; - - xml_writer& writer; - size_t bufsize; - xml_encoding encoding; - }; - - PUGI__FN void text_output_escaped(xml_buffered_writer& writer, const char_t* s, chartypex_t type) - { - while (*s) - { - const char_t* prev = s; - - // While *s is a usual symbol - while (!PUGI__IS_CHARTYPEX(*s, type)) ++s; - - writer.write(prev, static_cast(s - prev)); - - switch (*s) - { - case 0: break; - case '&': - writer.write('&', 'a', 'm', 'p', ';'); - ++s; - break; - case '<': - writer.write('&', 'l', 't', ';'); - ++s; - break; - case '>': - writer.write('&', 'g', 't', ';'); - ++s; - break; - case '"': - writer.write('&', 'q', 'u', 'o', 't', ';'); - ++s; - break; - default: // s is not a usual symbol - { - unsigned int ch = static_cast(*s++); - assert(ch < 32); - - writer.write('&', '#', static_cast((ch / 10) + '0'), static_cast((ch % 10) + '0'), ';'); - } - } - } - } - - PUGI__FN void text_output(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags) - { - if (flags & format_no_escapes) - writer.write(s); - else - text_output_escaped(writer, s, type); - } - - PUGI__FN void text_output_cdata(xml_buffered_writer& writer, const char_t* s) - { - do - { - writer.write('<', '!', '[', 'C', 'D'); - writer.write('A', 'T', 'A', '['); - - const char_t* prev = s; - - // look for ]]> sequence - we can't output it as is since it terminates CDATA - while (*s && !(s[0] == ']' && s[1] == ']' && s[2] == '>')) ++s; - - // skip ]] if we stopped at ]]>, > will go to the next CDATA section - if (*s) s += 2; - - writer.write(prev, static_cast(s - prev)); - - writer.write(']', ']', '>'); - } - while (*s); - } - - PUGI__FN void node_output_attributes(xml_buffered_writer& writer, const xml_node& node, unsigned int flags) - { - const char_t* default_name = PUGIXML_TEXT(":anonymous"); - - for (xml_attribute a = node.first_attribute(); a; a = a.next_attribute()) - { - writer.write(' '); - writer.write(a.name()[0] ? a.name() : default_name); - writer.write('=', '"'); - - text_output(writer, a.value(), ctx_special_attr, flags); - - writer.write('"'); - } - } - - PUGI__FN void node_output(xml_buffered_writer& writer, const xml_node& node, const char_t* indent, unsigned int flags, unsigned int depth) - { - const char_t* default_name = PUGIXML_TEXT(":anonymous"); - - if ((flags & format_indent) != 0 && (flags & format_raw) == 0) - for (unsigned int i = 0; i < depth; ++i) writer.write(indent); - - switch (node.type()) - { - case node_document: - { - for (xml_node n = node.first_child(); n; n = n.next_sibling()) - node_output(writer, n, indent, flags, depth); - break; - } - - case node_element: - { - const char_t* name = node.name()[0] ? node.name() : default_name; - - writer.write('<'); - writer.write(name); - - node_output_attributes(writer, node, flags); - - if (flags & format_raw) - { - if (!node.first_child()) - writer.write(' ', '/', '>'); - else - { - writer.write('>'); - - for (xml_node n = node.first_child(); n; n = n.next_sibling()) - node_output(writer, n, indent, flags, depth + 1); - - writer.write('<', '/'); - writer.write(name); - writer.write('>'); - } - } - else if (!node.first_child()) - writer.write(' ', '/', '>', '\n'); - else if (node.first_child() == node.last_child() && (node.first_child().type() == node_pcdata || node.first_child().type() == node_cdata)) - { - writer.write('>'); - - if (node.first_child().type() == node_pcdata) - text_output(writer, node.first_child().value(), ctx_special_pcdata, flags); - else - text_output_cdata(writer, node.first_child().value()); - - writer.write('<', '/'); - writer.write(name); - writer.write('>', '\n'); - } - else - { - writer.write('>', '\n'); - - for (xml_node n = node.first_child(); n; n = n.next_sibling()) - node_output(writer, n, indent, flags, depth + 1); - - if ((flags & format_indent) != 0 && (flags & format_raw) == 0) - for (unsigned int i = 0; i < depth; ++i) writer.write(indent); - - writer.write('<', '/'); - writer.write(name); - writer.write('>', '\n'); - } - - break; - } - - case node_pcdata: - text_output(writer, node.value(), ctx_special_pcdata, flags); - if ((flags & format_raw) == 0) writer.write('\n'); - break; - - case node_cdata: - text_output_cdata(writer, node.value()); - if ((flags & format_raw) == 0) writer.write('\n'); - break; - - case node_comment: - writer.write('<', '!', '-', '-'); - writer.write(node.value()); - writer.write('-', '-', '>'); - if ((flags & format_raw) == 0) writer.write('\n'); - break; - - case node_pi: - case node_declaration: - writer.write('<', '?'); - writer.write(node.name()[0] ? node.name() : default_name); - - if (node.type() == node_declaration) - { - node_output_attributes(writer, node, flags); - } - else if (node.value()[0]) - { - writer.write(' '); - writer.write(node.value()); - } - - writer.write('?', '>'); - if ((flags & format_raw) == 0) writer.write('\n'); - break; - - case node_doctype: - writer.write('<', '!', 'D', 'O', 'C'); - writer.write('T', 'Y', 'P', 'E'); - - if (node.value()[0]) - { - writer.write(' '); - writer.write(node.value()); - } - - writer.write('>'); - if ((flags & format_raw) == 0) writer.write('\n'); - break; - - default: - assert(!"Invalid node type"); - } - } - - inline bool has_declaration(const xml_node& node) - { - for (xml_node child = node.first_child(); child; child = child.next_sibling()) - { - xml_node_type type = child.type(); - - if (type == node_declaration) return true; - if (type == node_element) return false; - } - - return false; - } - - inline bool allow_insert_child(xml_node_type parent, xml_node_type child) - { - if (parent != node_document && parent != node_element) return false; - if (child == node_document || child == node_null) return false; - if (parent != node_document && (child == node_declaration || child == node_doctype)) return false; - - return true; - } - - PUGI__FN void recursive_copy_skip(xml_node& dest, const xml_node& source, const xml_node& skip) - { - assert(dest.type() == source.type()); - - switch (source.type()) - { - case node_element: - { - dest.set_name(source.name()); - - for (xml_attribute a = source.first_attribute(); a; a = a.next_attribute()) - dest.append_attribute(a.name()).set_value(a.value()); - - for (xml_node c = source.first_child(); c; c = c.next_sibling()) - { - if (c == skip) continue; - - xml_node cc = dest.append_child(c.type()); - assert(cc); - - recursive_copy_skip(cc, c, skip); - } - - break; - } - - case node_pcdata: - case node_cdata: - case node_comment: - case node_doctype: - dest.set_value(source.value()); - break; - - case node_pi: - dest.set_name(source.name()); - dest.set_value(source.value()); - break; - - case node_declaration: - { - dest.set_name(source.name()); - - for (xml_attribute a = source.first_attribute(); a; a = a.next_attribute()) - dest.append_attribute(a.name()).set_value(a.value()); - - break; - } - - default: - assert(!"Invalid node type"); - } - } - - inline bool is_text_node(xml_node_struct* node) - { - xml_node_type type = static_cast((node->header & impl::xml_memory_page_type_mask) + 1); - - return type == node_pcdata || type == node_cdata; - } - - // get value with conversion functions - PUGI__FN int get_value_int(const char_t* value, int def) - { - if (!value) return def; - - #ifdef PUGIXML_WCHAR_MODE - return static_cast(wcstol(value, 0, 10)); - #else - return static_cast(strtol(value, 0, 10)); - #endif - } - - PUGI__FN unsigned int get_value_uint(const char_t* value, unsigned int def) - { - if (!value) return def; - - #ifdef PUGIXML_WCHAR_MODE - return static_cast(wcstoul(value, 0, 10)); - #else - return static_cast(strtoul(value, 0, 10)); - #endif - } - - PUGI__FN double get_value_double(const char_t* value, double def) - { - if (!value) return def; - - #ifdef PUGIXML_WCHAR_MODE - return wcstod(value, 0); - #else - return strtod(value, 0); - #endif - } - - PUGI__FN float get_value_float(const char_t* value, float def) - { - if (!value) return def; - - #ifdef PUGIXML_WCHAR_MODE - return static_cast(wcstod(value, 0)); - #else - return static_cast(strtod(value, 0)); - #endif - } - - PUGI__FN bool get_value_bool(const char_t* value, bool def) - { - if (!value) return def; - - // only look at first char - char_t first = *value; - - // 1*, t* (true), T* (True), y* (yes), Y* (YES) - return (first == '1' || first == 't' || first == 'T' || first == 'y' || first == 'Y'); - } - - // set value with conversion functions - PUGI__FN bool set_value_buffer(char_t*& dest, uintptr_t& header, uintptr_t header_mask, char (&buf)[128]) - { - #ifdef PUGIXML_WCHAR_MODE - char_t wbuf[128]; - impl::widen_ascii(wbuf, buf); - - return strcpy_insitu(dest, header, header_mask, wbuf); - #else - return strcpy_insitu(dest, header, header_mask, buf); - #endif - } - - PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, int value) - { - char buf[128]; - sprintf(buf, "%d", value); - - return set_value_buffer(dest, header, header_mask, buf); - } - - PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, unsigned int value) - { - char buf[128]; - sprintf(buf, "%u", value); - - return set_value_buffer(dest, header, header_mask, buf); - } - - PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, double value) - { - char buf[128]; - sprintf(buf, "%g", value); - - return set_value_buffer(dest, header, header_mask, buf); - } - - PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, bool value) - { - return strcpy_insitu(dest, header, header_mask, value ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false")); - } - - // we need to get length of entire file to load it in memory; the only (relatively) sane way to do it is via seek/tell trick - PUGI__FN xml_parse_status get_file_size(FILE* file, size_t& out_result) - { - #if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 && !defined(_WIN32_WCE) - // there are 64-bit versions of fseek/ftell, let's use them - typedef __int64 length_type; - - _fseeki64(file, 0, SEEK_END); - length_type length = _ftelli64(file); - _fseeki64(file, 0, SEEK_SET); - #elif defined(__MINGW32__) && !defined(__NO_MINGW_LFS) && !defined(__STRICT_ANSI__) - // there are 64-bit versions of fseek/ftell, let's use them - typedef off64_t length_type; - - fseeko64(file, 0, SEEK_END); - length_type length = ftello64(file); - fseeko64(file, 0, SEEK_SET); - #else - // if this is a 32-bit OS, long is enough; if this is a unix system, long is 64-bit, which is enough; otherwise we can't do anything anyway. - typedef long length_type; - - fseek(file, 0, SEEK_END); - length_type length = ftell(file); - fseek(file, 0, SEEK_SET); - #endif - - // check for I/O errors - if (length < 0) return status_io_error; - - // check for overflow - size_t result = static_cast(length); - - if (static_cast(result) != length) return status_out_of_memory; - - // finalize - out_result = result; - - return status_ok; - } - - PUGI__FN xml_parse_result load_file_impl(xml_document& doc, FILE* file, unsigned int options, xml_encoding encoding) - { - if (!file) return make_parse_result(status_file_not_found); - - // get file size (can result in I/O errors) - size_t size = 0; - xml_parse_status size_status = get_file_size(file, size); - - if (size_status != status_ok) - { - fclose(file); - return make_parse_result(size_status); - } - - // allocate buffer for the whole file - char* contents = static_cast(xml_memory::allocate(size > 0 ? size : 1)); - - if (!contents) - { - fclose(file); - return make_parse_result(status_out_of_memory); - } - - // read file in memory - size_t read_size = fread(contents, 1, size, file); - fclose(file); - - if (read_size != size) - { - xml_memory::deallocate(contents); - return make_parse_result(status_io_error); - } - - return doc.load_buffer_inplace_own(contents, size, options, encoding); - } - -#ifndef PUGIXML_NO_STL - template struct xml_stream_chunk - { - static xml_stream_chunk* create() - { - void* memory = xml_memory::allocate(sizeof(xml_stream_chunk)); - - return new (memory) xml_stream_chunk(); - } - - static void destroy(void* ptr) - { - xml_stream_chunk* chunk = static_cast(ptr); - - // free chunk chain - while (chunk) - { - xml_stream_chunk* next = chunk->next; - xml_memory::deallocate(chunk); - chunk = next; - } - } - - xml_stream_chunk(): next(0), size(0) - { - } - - xml_stream_chunk* next; - size_t size; - - T data[xml_memory_page_size / sizeof(T)]; - }; - - template PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream& stream, void** out_buffer, size_t* out_size) - { - buffer_holder chunks(0, xml_stream_chunk::destroy); - - // read file to a chunk list - size_t total = 0; - xml_stream_chunk* last = 0; - - while (!stream.eof()) - { - // allocate new chunk - xml_stream_chunk* chunk = xml_stream_chunk::create(); - if (!chunk) return status_out_of_memory; - - // append chunk to list - if (last) last = last->next = chunk; - else chunks.data = last = chunk; - - // read data to chunk - stream.read(chunk->data, static_cast(sizeof(chunk->data) / sizeof(T))); - chunk->size = static_cast(stream.gcount()) * sizeof(T); - - // read may set failbit | eofbit in case gcount() is less than read length, so check for other I/O errors - if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; - - // guard against huge files (chunk size is small enough to make this overflow check work) - if (total + chunk->size < total) return status_out_of_memory; - total += chunk->size; - } - - // copy chunk list to a contiguous buffer - char* buffer = static_cast(xml_memory::allocate(total)); - if (!buffer) return status_out_of_memory; - - char* write = buffer; - - for (xml_stream_chunk* chunk = static_cast*>(chunks.data); chunk; chunk = chunk->next) - { - assert(write + chunk->size <= buffer + total); - memcpy(write, chunk->data, chunk->size); - write += chunk->size; - } - - assert(write == buffer + total); - - // return buffer - *out_buffer = buffer; - *out_size = total; - - return status_ok; - } - - template PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream& stream, void** out_buffer, size_t* out_size) - { - // get length of remaining data in stream - typename std::basic_istream::pos_type pos = stream.tellg(); - stream.seekg(0, std::ios::end); - std::streamoff length = stream.tellg() - pos; - stream.seekg(pos); - - if (stream.fail() || pos < 0) return status_io_error; - - // guard against huge files - size_t read_length = static_cast(length); - - if (static_cast(read_length) != length || length < 0) return status_out_of_memory; - - // read stream data into memory (guard against stream exceptions with buffer holder) - buffer_holder buffer(xml_memory::allocate((read_length > 0 ? read_length : 1) * sizeof(T)), xml_memory::deallocate); - if (!buffer.data) return status_out_of_memory; - - stream.read(static_cast(buffer.data), static_cast(read_length)); - - // read may set failbit | eofbit in case gcount() is less than read_length (i.e. line ending conversion), so check for other I/O errors - if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; - - // return buffer - size_t actual_length = static_cast(stream.gcount()); - assert(actual_length <= read_length); - - *out_buffer = buffer.release(); - *out_size = actual_length * sizeof(T); - - return status_ok; - } - - template PUGI__FN xml_parse_result load_stream_impl(xml_document& doc, std::basic_istream& stream, unsigned int options, xml_encoding encoding) - { - void* buffer = 0; - size_t size = 0; - - // load stream to memory (using seek-based implementation if possible, since it's faster and takes less memory) - xml_parse_status status = (stream.tellg() < 0) ? load_stream_data_noseek(stream, &buffer, &size) : load_stream_data_seek(stream, &buffer, &size); - if (status != status_ok) return make_parse_result(status); - - return doc.load_buffer_inplace_own(buffer, size, options, encoding); - } -#endif - -#if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) || (defined(__MINGW32__) && !defined(__STRICT_ANSI__)) - PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) - { - return _wfopen(path, mode); - } -#else - PUGI__FN char* convert_path_heap(const wchar_t* str) - { - assert(str); - - // first pass: get length in utf8 characters - size_t length = wcslen(str); - size_t size = as_utf8_begin(str, length); - - // allocate resulting string - char* result = static_cast(xml_memory::allocate(size + 1)); - if (!result) return 0; - - // second pass: convert to utf8 - as_utf8_end(result, size, str, length); - - return result; - } - - PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) - { - // there is no standard function to open wide paths, so our best bet is to try utf8 path - char* path_utf8 = convert_path_heap(path); - if (!path_utf8) return 0; - - // convert mode to ASCII (we mirror _wfopen interface) - char mode_ascii[4] = {0}; - for (size_t i = 0; mode[i]; ++i) mode_ascii[i] = static_cast(mode[i]); - - // try to open the utf8 path - FILE* result = fopen(path_utf8, mode_ascii); - - // free dummy buffer - xml_memory::deallocate(path_utf8); - - return result; - } -#endif - - PUGI__FN bool save_file_impl(const xml_document& doc, FILE* file, const char_t* indent, unsigned int flags, xml_encoding encoding) - { - if (!file) return false; - - xml_writer_file writer(file); - doc.save(writer, indent, flags, encoding); - - int result = ferror(file); - - fclose(file); - - return result == 0; - } -PUGI__NS_END - -namespace pugi -{ - PUGI__FN xml_writer_file::xml_writer_file(void* file_): file(file_) - { - } - - PUGI__FN void xml_writer_file::write(const void* data, size_t size) - { - size_t result = fwrite(data, 1, size, static_cast(file)); - (void)!result; // unfortunately we can't do proper error handling here - } - -#ifndef PUGIXML_NO_STL - PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(&stream), wide_stream(0) - { - } - - PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(0), wide_stream(&stream) - { - } - - PUGI__FN void xml_writer_stream::write(const void* data, size_t size) - { - if (narrow_stream) - { - assert(!wide_stream); - narrow_stream->write(reinterpret_cast(data), static_cast(size)); - } - else - { - assert(wide_stream); - assert(size % sizeof(wchar_t) == 0); - - wide_stream->write(reinterpret_cast(data), static_cast(size / sizeof(wchar_t))); - } - } -#endif - - PUGI__FN xml_tree_walker::xml_tree_walker(): _depth(0) - { - } - - PUGI__FN xml_tree_walker::~xml_tree_walker() - { - } - - PUGI__FN int xml_tree_walker::depth() const - { - return _depth; - } - - PUGI__FN bool xml_tree_walker::begin(xml_node&) - { - return true; - } - - PUGI__FN bool xml_tree_walker::end(xml_node&) - { - return true; - } - - PUGI__FN xml_attribute::xml_attribute(): _attr(0) - { - } - - PUGI__FN xml_attribute::xml_attribute(xml_attribute_struct* attr): _attr(attr) - { - } - - PUGI__FN static void unspecified_bool_xml_attribute(xml_attribute***) - { - } - - PUGI__FN xml_attribute::operator xml_attribute::unspecified_bool_type() const - { - return _attr ? unspecified_bool_xml_attribute : 0; - } - - PUGI__FN bool xml_attribute::operator!() const - { - return !_attr; - } - - PUGI__FN bool xml_attribute::operator==(const xml_attribute& r) const - { - return (_attr == r._attr); - } - - PUGI__FN bool xml_attribute::operator!=(const xml_attribute& r) const - { - return (_attr != r._attr); - } - - PUGI__FN bool xml_attribute::operator<(const xml_attribute& r) const - { - return (_attr < r._attr); - } - - PUGI__FN bool xml_attribute::operator>(const xml_attribute& r) const - { - return (_attr > r._attr); - } - - PUGI__FN bool xml_attribute::operator<=(const xml_attribute& r) const - { - return (_attr <= r._attr); - } - - PUGI__FN bool xml_attribute::operator>=(const xml_attribute& r) const - { - return (_attr >= r._attr); - } - - PUGI__FN xml_attribute xml_attribute::next_attribute() const - { - return _attr ? xml_attribute(_attr->next_attribute) : xml_attribute(); - } - - PUGI__FN xml_attribute xml_attribute::previous_attribute() const - { - return _attr && _attr->prev_attribute_c->next_attribute ? xml_attribute(_attr->prev_attribute_c) : xml_attribute(); - } - - PUGI__FN const char_t* xml_attribute::as_string(const char_t* def) const - { - return (_attr && _attr->value) ? _attr->value : def; - } - - PUGI__FN int xml_attribute::as_int(int def) const - { - return impl::get_value_int(_attr ? _attr->value : 0, def); - } - - PUGI__FN unsigned int xml_attribute::as_uint(unsigned int def) const - { - return impl::get_value_uint(_attr ? _attr->value : 0, def); - } - - PUGI__FN double xml_attribute::as_double(double def) const - { - return impl::get_value_double(_attr ? _attr->value : 0, def); - } - - PUGI__FN float xml_attribute::as_float(float def) const - { - return impl::get_value_float(_attr ? _attr->value : 0, def); - } - - PUGI__FN bool xml_attribute::as_bool(bool def) const - { - return impl::get_value_bool(_attr ? _attr->value : 0, def); - } - - PUGI__FN bool xml_attribute::empty() const - { - return !_attr; - } - - PUGI__FN const char_t* xml_attribute::name() const - { - return (_attr && _attr->name) ? _attr->name : PUGIXML_TEXT(""); - } - - PUGI__FN const char_t* xml_attribute::value() const - { - return (_attr && _attr->value) ? _attr->value : PUGIXML_TEXT(""); - } - - PUGI__FN size_t xml_attribute::hash_value() const - { - return static_cast(reinterpret_cast(_attr) / sizeof(xml_attribute_struct)); - } - - PUGI__FN xml_attribute_struct* xml_attribute::internal_object() const - { - return _attr; - } - - PUGI__FN xml_attribute& xml_attribute::operator=(const char_t* rhs) - { - set_value(rhs); - return *this; - } - - PUGI__FN xml_attribute& xml_attribute::operator=(int rhs) - { - set_value(rhs); - return *this; - } - - PUGI__FN xml_attribute& xml_attribute::operator=(unsigned int rhs) - { - set_value(rhs); - return *this; - } - - PUGI__FN xml_attribute& xml_attribute::operator=(double rhs) - { - set_value(rhs); - return *this; - } - - PUGI__FN xml_attribute& xml_attribute::operator=(bool rhs) - { - set_value(rhs); - return *this; - } - - PUGI__FN bool xml_attribute::set_name(const char_t* rhs) - { - if (!_attr) return false; - - return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs); - } - - PUGI__FN bool xml_attribute::set_value(const char_t* rhs) - { - if (!_attr) return false; - - return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); - } - - PUGI__FN bool xml_attribute::set_value(int rhs) - { - if (!_attr) return false; - - return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); - } - - PUGI__FN bool xml_attribute::set_value(unsigned int rhs) - { - if (!_attr) return false; - - return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); - } - - PUGI__FN bool xml_attribute::set_value(double rhs) - { - if (!_attr) return false; - - return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); - } - - PUGI__FN bool xml_attribute::set_value(bool rhs) - { - if (!_attr) return false; - - return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); - } - -#ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xml_attribute& lhs, bool rhs) - { - return (bool)lhs && rhs; - } - - PUGI__FN bool operator||(const xml_attribute& lhs, bool rhs) - { - return (bool)lhs || rhs; - } -#endif - - PUGI__FN xml_node::xml_node(): _root(0) - { - } - - PUGI__FN xml_node::xml_node(xml_node_struct* p): _root(p) - { - } - - PUGI__FN static void unspecified_bool_xml_node(xml_node***) - { - } - - PUGI__FN xml_node::operator xml_node::unspecified_bool_type() const - { - return _root ? unspecified_bool_xml_node : 0; - } - - PUGI__FN bool xml_node::operator!() const - { - return !_root; - } - - PUGI__FN xml_node::iterator xml_node::begin() const - { - return iterator(_root ? _root->first_child : 0, _root); - } - - PUGI__FN xml_node::iterator xml_node::end() const - { - return iterator(0, _root); - } - - PUGI__FN xml_node::attribute_iterator xml_node::attributes_begin() const - { - return attribute_iterator(_root ? _root->first_attribute : 0, _root); - } - - PUGI__FN xml_node::attribute_iterator xml_node::attributes_end() const - { - return attribute_iterator(0, _root); - } - - PUGI__FN xml_object_range xml_node::children() const - { - return xml_object_range(begin(), end()); - } - - PUGI__FN xml_object_range xml_node::children(const char_t* name_) const - { - return xml_object_range(xml_named_node_iterator(child(name_), name_), xml_named_node_iterator()); - } - - PUGI__FN xml_object_range xml_node::attributes() const - { - return xml_object_range(attributes_begin(), attributes_end()); - } - - PUGI__FN bool xml_node::operator==(const xml_node& r) const - { - return (_root == r._root); - } - - PUGI__FN bool xml_node::operator!=(const xml_node& r) const - { - return (_root != r._root); - } - - PUGI__FN bool xml_node::operator<(const xml_node& r) const - { - return (_root < r._root); - } - - PUGI__FN bool xml_node::operator>(const xml_node& r) const - { - return (_root > r._root); - } - - PUGI__FN bool xml_node::operator<=(const xml_node& r) const - { - return (_root <= r._root); - } - - PUGI__FN bool xml_node::operator>=(const xml_node& r) const - { - return (_root >= r._root); - } - - PUGI__FN bool xml_node::empty() const - { - return !_root; - } - - PUGI__FN const char_t* xml_node::name() const - { - return (_root && _root->name) ? _root->name : PUGIXML_TEXT(""); - } - - PUGI__FN xml_node_type xml_node::type() const - { - return _root ? static_cast((_root->header & impl::xml_memory_page_type_mask) + 1) : node_null; - } - - PUGI__FN const char_t* xml_node::value() const - { - return (_root && _root->value) ? _root->value : PUGIXML_TEXT(""); - } - - PUGI__FN xml_node xml_node::child(const char_t* name_) const - { - if (!_root) return xml_node(); - - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - if (i->name && impl::strequal(name_, i->name)) return xml_node(i); - - return xml_node(); - } - - PUGI__FN xml_attribute xml_node::attribute(const char_t* name_) const - { - if (!_root) return xml_attribute(); - - for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute) - if (i->name && impl::strequal(name_, i->name)) - return xml_attribute(i); - - return xml_attribute(); - } - - PUGI__FN xml_node xml_node::next_sibling(const char_t* name_) const - { - if (!_root) return xml_node(); - - for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling) - if (i->name && impl::strequal(name_, i->name)) return xml_node(i); - - return xml_node(); - } - - PUGI__FN xml_node xml_node::next_sibling() const - { - if (!_root) return xml_node(); - - if (_root->next_sibling) return xml_node(_root->next_sibling); - else return xml_node(); - } - - PUGI__FN xml_node xml_node::previous_sibling(const char_t* name_) const - { - if (!_root) return xml_node(); - - for (xml_node_struct* i = _root->prev_sibling_c; i->next_sibling; i = i->prev_sibling_c) - if (i->name && impl::strequal(name_, i->name)) return xml_node(i); - - return xml_node(); - } - - PUGI__FN xml_node xml_node::previous_sibling() const - { - if (!_root) return xml_node(); - - if (_root->prev_sibling_c->next_sibling) return xml_node(_root->prev_sibling_c); - else return xml_node(); - } - - PUGI__FN xml_node xml_node::parent() const - { - return _root ? xml_node(_root->parent) : xml_node(); - } - - PUGI__FN xml_node xml_node::root() const - { - if (!_root) return xml_node(); - - impl::xml_memory_page* page = reinterpret_cast(_root->header & impl::xml_memory_page_pointer_mask); - - return xml_node(static_cast(page->allocator)); - } - - PUGI__FN xml_text xml_node::text() const - { - return xml_text(_root); - } - - PUGI__FN const char_t* xml_node::child_value() const - { - if (!_root) return PUGIXML_TEXT(""); - - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - if (i->value && impl::is_text_node(i)) - return i->value; - - return PUGIXML_TEXT(""); - } - - PUGI__FN const char_t* xml_node::child_value(const char_t* name_) const - { - return child(name_).child_value(); - } - - PUGI__FN xml_attribute xml_node::first_attribute() const - { - return _root ? xml_attribute(_root->first_attribute) : xml_attribute(); - } - - PUGI__FN xml_attribute xml_node::last_attribute() const - { - return _root && _root->first_attribute ? xml_attribute(_root->first_attribute->prev_attribute_c) : xml_attribute(); - } - - PUGI__FN xml_node xml_node::first_child() const - { - return _root ? xml_node(_root->first_child) : xml_node(); - } - - PUGI__FN xml_node xml_node::last_child() const - { - return _root && _root->first_child ? xml_node(_root->first_child->prev_sibling_c) : xml_node(); - } - - PUGI__FN bool xml_node::set_name(const char_t* rhs) - { - switch (type()) - { - case node_pi: - case node_declaration: - case node_element: - return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs); - - default: - return false; - } - } - - PUGI__FN bool xml_node::set_value(const char_t* rhs) - { - switch (type()) - { - case node_pi: - case node_cdata: - case node_pcdata: - case node_comment: - case node_doctype: - return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs); - - default: - return false; - } - } - - PUGI__FN xml_attribute xml_node::append_attribute(const char_t* name_) - { - if (type() != node_element && type() != node_declaration) return xml_attribute(); - - xml_attribute a(impl::append_attribute_ll(_root, impl::get_allocator(_root))); - a.set_name(name_); - - return a; - } - - PUGI__FN xml_attribute xml_node::prepend_attribute(const char_t* name_) - { - if (type() != node_element && type() != node_declaration) return xml_attribute(); - - xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); - if (!a) return xml_attribute(); - - a.set_name(name_); - - xml_attribute_struct* head = _root->first_attribute; - - if (head) - { - a._attr->prev_attribute_c = head->prev_attribute_c; - head->prev_attribute_c = a._attr; - } - else - a._attr->prev_attribute_c = a._attr; - - a._attr->next_attribute = head; - _root->first_attribute = a._attr; - - return a; - } - - PUGI__FN xml_attribute xml_node::insert_attribute_before(const char_t* name_, const xml_attribute& attr) - { - if ((type() != node_element && type() != node_declaration) || attr.empty()) return xml_attribute(); - - // check that attribute belongs to *this - xml_attribute_struct* cur = attr._attr; - - while (cur->prev_attribute_c->next_attribute) cur = cur->prev_attribute_c; - - if (cur != _root->first_attribute) return xml_attribute(); - - xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); - if (!a) return xml_attribute(); - - a.set_name(name_); - - if (attr._attr->prev_attribute_c->next_attribute) - attr._attr->prev_attribute_c->next_attribute = a._attr; - else - _root->first_attribute = a._attr; - - a._attr->prev_attribute_c = attr._attr->prev_attribute_c; - a._attr->next_attribute = attr._attr; - attr._attr->prev_attribute_c = a._attr; - - return a; - } - - PUGI__FN xml_attribute xml_node::insert_attribute_after(const char_t* name_, const xml_attribute& attr) - { - if ((type() != node_element && type() != node_declaration) || attr.empty()) return xml_attribute(); - - // check that attribute belongs to *this - xml_attribute_struct* cur = attr._attr; - - while (cur->prev_attribute_c->next_attribute) cur = cur->prev_attribute_c; - - if (cur != _root->first_attribute) return xml_attribute(); - - xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); - if (!a) return xml_attribute(); - - a.set_name(name_); - - if (attr._attr->next_attribute) - attr._attr->next_attribute->prev_attribute_c = a._attr; - else - _root->first_attribute->prev_attribute_c = a._attr; - - a._attr->next_attribute = attr._attr->next_attribute; - a._attr->prev_attribute_c = attr._attr; - attr._attr->next_attribute = a._attr; - - return a; - } - - PUGI__FN xml_attribute xml_node::append_copy(const xml_attribute& proto) - { - if (!proto) return xml_attribute(); - - xml_attribute result = append_attribute(proto.name()); - result.set_value(proto.value()); - - return result; - } - - PUGI__FN xml_attribute xml_node::prepend_copy(const xml_attribute& proto) - { - if (!proto) return xml_attribute(); - - xml_attribute result = prepend_attribute(proto.name()); - result.set_value(proto.value()); - - return result; - } - - PUGI__FN xml_attribute xml_node::insert_copy_after(const xml_attribute& proto, const xml_attribute& attr) - { - if (!proto) return xml_attribute(); - - xml_attribute result = insert_attribute_after(proto.name(), attr); - result.set_value(proto.value()); - - return result; - } - - PUGI__FN xml_attribute xml_node::insert_copy_before(const xml_attribute& proto, const xml_attribute& attr) - { - if (!proto) return xml_attribute(); - - xml_attribute result = insert_attribute_before(proto.name(), attr); - result.set_value(proto.value()); - - return result; - } - - PUGI__FN xml_node xml_node::append_child(xml_node_type type_) - { - if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); - - xml_node n(impl::append_node(_root, impl::get_allocator(_root), type_)); - - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); - - return n; - } - - PUGI__FN xml_node xml_node::prepend_child(xml_node_type type_) - { - if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); - - xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); - if (!n) return xml_node(); - - n._root->parent = _root; - - xml_node_struct* head = _root->first_child; - - if (head) - { - n._root->prev_sibling_c = head->prev_sibling_c; - head->prev_sibling_c = n._root; - } - else - n._root->prev_sibling_c = n._root; - - n._root->next_sibling = head; - _root->first_child = n._root; - - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); - - return n; - } - - PUGI__FN xml_node xml_node::insert_child_before(xml_node_type type_, const xml_node& node) - { - if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); - if (!node._root || node._root->parent != _root) return xml_node(); - - xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); - if (!n) return xml_node(); - - n._root->parent = _root; - - if (node._root->prev_sibling_c->next_sibling) - node._root->prev_sibling_c->next_sibling = n._root; - else - _root->first_child = n._root; - - n._root->prev_sibling_c = node._root->prev_sibling_c; - n._root->next_sibling = node._root; - node._root->prev_sibling_c = n._root; - - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); - - return n; - } - - PUGI__FN xml_node xml_node::insert_child_after(xml_node_type type_, const xml_node& node) - { - if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); - if (!node._root || node._root->parent != _root) return xml_node(); - - xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); - if (!n) return xml_node(); - - n._root->parent = _root; - - if (node._root->next_sibling) - node._root->next_sibling->prev_sibling_c = n._root; - else - _root->first_child->prev_sibling_c = n._root; - - n._root->next_sibling = node._root->next_sibling; - n._root->prev_sibling_c = node._root; - node._root->next_sibling = n._root; - - if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); - - return n; - } - - PUGI__FN xml_node xml_node::append_child(const char_t* name_) - { - xml_node result = append_child(node_element); - - result.set_name(name_); - - return result; - } - - PUGI__FN xml_node xml_node::prepend_child(const char_t* name_) - { - xml_node result = prepend_child(node_element); - - result.set_name(name_); - - return result; - } - - PUGI__FN xml_node xml_node::insert_child_after(const char_t* name_, const xml_node& node) - { - xml_node result = insert_child_after(node_element, node); - - result.set_name(name_); - - return result; - } - - PUGI__FN xml_node xml_node::insert_child_before(const char_t* name_, const xml_node& node) - { - xml_node result = insert_child_before(node_element, node); - - result.set_name(name_); - - return result; - } - - PUGI__FN xml_node xml_node::append_copy(const xml_node& proto) - { - xml_node result = append_child(proto.type()); - - if (result) impl::recursive_copy_skip(result, proto, result); - - return result; - } - - PUGI__FN xml_node xml_node::prepend_copy(const xml_node& proto) - { - xml_node result = prepend_child(proto.type()); - - if (result) impl::recursive_copy_skip(result, proto, result); - - return result; - } - - PUGI__FN xml_node xml_node::insert_copy_after(const xml_node& proto, const xml_node& node) - { - xml_node result = insert_child_after(proto.type(), node); - - if (result) impl::recursive_copy_skip(result, proto, result); - - return result; - } - - PUGI__FN xml_node xml_node::insert_copy_before(const xml_node& proto, const xml_node& node) - { - xml_node result = insert_child_before(proto.type(), node); - - if (result) impl::recursive_copy_skip(result, proto, result); - - return result; - } - - PUGI__FN bool xml_node::remove_attribute(const char_t* name_) - { - return remove_attribute(attribute(name_)); - } - - PUGI__FN bool xml_node::remove_attribute(const xml_attribute& a) - { - if (!_root || !a._attr) return false; - - // check that attribute belongs to *this - xml_attribute_struct* attr = a._attr; - - while (attr->prev_attribute_c->next_attribute) attr = attr->prev_attribute_c; - - if (attr != _root->first_attribute) return false; - - if (a._attr->next_attribute) a._attr->next_attribute->prev_attribute_c = a._attr->prev_attribute_c; - else if (_root->first_attribute) _root->first_attribute->prev_attribute_c = a._attr->prev_attribute_c; - - if (a._attr->prev_attribute_c->next_attribute) a._attr->prev_attribute_c->next_attribute = a._attr->next_attribute; - else _root->first_attribute = a._attr->next_attribute; - - impl::destroy_attribute(a._attr, impl::get_allocator(_root)); - - return true; - } - - PUGI__FN bool xml_node::remove_child(const char_t* name_) - { - return remove_child(child(name_)); - } - - PUGI__FN bool xml_node::remove_child(const xml_node& n) - { - if (!_root || !n._root || n._root->parent != _root) return false; - - if (n._root->next_sibling) n._root->next_sibling->prev_sibling_c = n._root->prev_sibling_c; - else if (_root->first_child) _root->first_child->prev_sibling_c = n._root->prev_sibling_c; - - if (n._root->prev_sibling_c->next_sibling) n._root->prev_sibling_c->next_sibling = n._root->next_sibling; - else _root->first_child = n._root->next_sibling; - - impl::destroy_node(n._root, impl::get_allocator(_root)); - - return true; - } - - PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* name_, const char_t* attr_name, const char_t* attr_value) const - { - if (!_root) return xml_node(); - - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - if (i->name && impl::strequal(name_, i->name)) - { - for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) - if (impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value)) - return xml_node(i); - } - - return xml_node(); - } - - PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const - { - if (!_root) return xml_node(); - - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) - if (impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value)) - return xml_node(i); - - return xml_node(); - } - -#ifndef PUGIXML_NO_STL - PUGI__FN string_t xml_node::path(char_t delimiter) const - { - xml_node cursor = *this; // Make a copy. - - string_t result = cursor.name(); - - while (cursor.parent()) - { - cursor = cursor.parent(); - - string_t temp = cursor.name(); - temp += delimiter; - temp += result; - result.swap(temp); - } - - return result; - } -#endif - - PUGI__FN xml_node xml_node::first_element_by_path(const char_t* path_, char_t delimiter) const - { - xml_node found = *this; // Current search context. - - if (!_root || !path_ || !path_[0]) return found; - - if (path_[0] == delimiter) - { - // Absolute path; e.g. '/foo/bar' - found = found.root(); - ++path_; - } - - const char_t* path_segment = path_; - - while (*path_segment == delimiter) ++path_segment; - - const char_t* path_segment_end = path_segment; - - while (*path_segment_end && *path_segment_end != delimiter) ++path_segment_end; - - if (path_segment == path_segment_end) return found; - - const char_t* next_segment = path_segment_end; - - while (*next_segment == delimiter) ++next_segment; - - if (*path_segment == '.' && path_segment + 1 == path_segment_end) - return found.first_element_by_path(next_segment, delimiter); - else if (*path_segment == '.' && *(path_segment+1) == '.' && path_segment + 2 == path_segment_end) - return found.parent().first_element_by_path(next_segment, delimiter); - else - { - for (xml_node_struct* j = found._root->first_child; j; j = j->next_sibling) - { - if (j->name && impl::strequalrange(j->name, path_segment, static_cast(path_segment_end - path_segment))) - { - xml_node subsearch = xml_node(j).first_element_by_path(next_segment, delimiter); - - if (subsearch) return subsearch; - } - } - - return xml_node(); - } - } - - PUGI__FN bool xml_node::traverse(xml_tree_walker& walker) - { - walker._depth = -1; - - xml_node arg_begin = *this; - if (!walker.begin(arg_begin)) return false; - - xml_node cur = first_child(); - - if (cur) - { - ++walker._depth; - - do - { - xml_node arg_for_each = cur; - if (!walker.for_each(arg_for_each)) - return false; - - if (cur.first_child()) - { - ++walker._depth; - cur = cur.first_child(); - } - else if (cur.next_sibling()) - cur = cur.next_sibling(); - else - { - // Borland C++ workaround - while (!cur.next_sibling() && cur != *this && !cur.parent().empty()) - { - --walker._depth; - cur = cur.parent(); - } - - if (cur != *this) - cur = cur.next_sibling(); - } - } - while (cur && cur != *this); - } - - assert(walker._depth == -1); - - xml_node arg_end = *this; - return walker.end(arg_end); - } - - PUGI__FN size_t xml_node::hash_value() const - { - return static_cast(reinterpret_cast(_root) / sizeof(xml_node_struct)); - } - - PUGI__FN xml_node_struct* xml_node::internal_object() const - { - return _root; - } - - PUGI__FN void xml_node::print(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const - { - if (!_root) return; - - impl::xml_buffered_writer buffered_writer(writer, encoding); - - impl::node_output(buffered_writer, *this, indent, flags, depth); - } - -#ifndef PUGIXML_NO_STL - PUGI__FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const - { - xml_writer_stream writer(stream); - - print(writer, indent, flags, encoding, depth); - } - - PUGI__FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, unsigned int depth) const - { - xml_writer_stream writer(stream); - - print(writer, indent, flags, encoding_wchar, depth); - } -#endif - - PUGI__FN ptrdiff_t xml_node::offset_debug() const - { - xml_node_struct* r = root()._root; - - if (!r) return -1; - - const char_t* buffer = static_cast(r)->buffer; - - if (!buffer) return -1; - - switch (type()) - { - case node_document: - return 0; - - case node_element: - case node_declaration: - case node_pi: - return (_root->header & impl::xml_memory_page_name_allocated_mask) ? -1 : _root->name - buffer; - - case node_pcdata: - case node_cdata: - case node_comment: - case node_doctype: - return (_root->header & impl::xml_memory_page_value_allocated_mask) ? -1 : _root->value - buffer; - - default: - return -1; - } - } - -#ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xml_node& lhs, bool rhs) - { - return (bool)lhs && rhs; - } - - PUGI__FN bool operator||(const xml_node& lhs, bool rhs) - { - return (bool)lhs || rhs; - } -#endif - - PUGI__FN xml_text::xml_text(xml_node_struct* root): _root(root) - { - } - - PUGI__FN xml_node_struct* xml_text::_data() const - { - if (!_root || impl::is_text_node(_root)) return _root; - - for (xml_node_struct* node = _root->first_child; node; node = node->next_sibling) - if (impl::is_text_node(node)) - return node; - - return 0; - } - - PUGI__FN xml_node_struct* xml_text::_data_new() - { - xml_node_struct* d = _data(); - if (d) return d; - - return xml_node(_root).append_child(node_pcdata).internal_object(); - } - - PUGI__FN xml_text::xml_text(): _root(0) - { - } - - PUGI__FN static void unspecified_bool_xml_text(xml_text***) - { - } - - PUGI__FN xml_text::operator xml_text::unspecified_bool_type() const - { - return _data() ? unspecified_bool_xml_text : 0; - } - - PUGI__FN bool xml_text::operator!() const - { - return !_data(); - } - - PUGI__FN bool xml_text::empty() const - { - return _data() == 0; - } - - PUGI__FN const char_t* xml_text::get() const - { - xml_node_struct* d = _data(); - - return (d && d->value) ? d->value : PUGIXML_TEXT(""); - } - - PUGI__FN const char_t* xml_text::as_string(const char_t* def) const - { - xml_node_struct* d = _data(); - - return (d && d->value) ? d->value : def; - } - - PUGI__FN int xml_text::as_int(int def) const - { - xml_node_struct* d = _data(); - - return impl::get_value_int(d ? d->value : 0, def); - } - - PUGI__FN unsigned int xml_text::as_uint(unsigned int def) const - { - xml_node_struct* d = _data(); - - return impl::get_value_uint(d ? d->value : 0, def); - } - - PUGI__FN double xml_text::as_double(double def) const - { - xml_node_struct* d = _data(); - - return impl::get_value_double(d ? d->value : 0, def); - } - - PUGI__FN float xml_text::as_float(float def) const - { - xml_node_struct* d = _data(); - - return impl::get_value_float(d ? d->value : 0, def); - } - - PUGI__FN bool xml_text::as_bool(bool def) const - { - xml_node_struct* d = _data(); - - return impl::get_value_bool(d ? d->value : 0, def); - } - - PUGI__FN bool xml_text::set(const char_t* rhs) - { - xml_node_struct* dn = _data_new(); - - return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; - } - - PUGI__FN bool xml_text::set(int rhs) - { - xml_node_struct* dn = _data_new(); - - return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; - } - - PUGI__FN bool xml_text::set(unsigned int rhs) - { - xml_node_struct* dn = _data_new(); - - return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; - } - - PUGI__FN bool xml_text::set(double rhs) - { - xml_node_struct* dn = _data_new(); - - return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; - } - - PUGI__FN bool xml_text::set(bool rhs) - { - xml_node_struct* dn = _data_new(); - - return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; - } - - PUGI__FN xml_text& xml_text::operator=(const char_t* rhs) - { - set(rhs); - return *this; - } - - PUGI__FN xml_text& xml_text::operator=(int rhs) - { - set(rhs); - return *this; - } - - PUGI__FN xml_text& xml_text::operator=(unsigned int rhs) - { - set(rhs); - return *this; - } - - PUGI__FN xml_text& xml_text::operator=(double rhs) - { - set(rhs); - return *this; - } - - PUGI__FN xml_text& xml_text::operator=(bool rhs) - { - set(rhs); - return *this; - } - - PUGI__FN xml_node xml_text::data() const - { - return xml_node(_data()); - } - -#ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xml_text& lhs, bool rhs) - { - return (bool)lhs && rhs; - } - - PUGI__FN bool operator||(const xml_text& lhs, bool rhs) - { - return (bool)lhs || rhs; - } -#endif - - PUGI__FN xml_node_iterator::xml_node_iterator() - { - } - - PUGI__FN xml_node_iterator::xml_node_iterator(const xml_node& node): _wrap(node), _parent(node.parent()) - { - } - - PUGI__FN xml_node_iterator::xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) - { - } - - PUGI__FN bool xml_node_iterator::operator==(const xml_node_iterator& rhs) const - { - return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; - } - - PUGI__FN bool xml_node_iterator::operator!=(const xml_node_iterator& rhs) const - { - return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; - } - - PUGI__FN xml_node& xml_node_iterator::operator*() const - { - assert(_wrap._root); - return _wrap; - } - - PUGI__FN xml_node* xml_node_iterator::operator->() const - { - assert(_wrap._root); - return const_cast(&_wrap); // BCC32 workaround - } - - PUGI__FN const xml_node_iterator& xml_node_iterator::operator++() - { - assert(_wrap._root); - _wrap._root = _wrap._root->next_sibling; - return *this; - } - - PUGI__FN xml_node_iterator xml_node_iterator::operator++(int) - { - xml_node_iterator temp = *this; - ++*this; - return temp; - } - - PUGI__FN const xml_node_iterator& xml_node_iterator::operator--() - { - _wrap = _wrap._root ? _wrap.previous_sibling() : _parent.last_child(); - return *this; - } - - PUGI__FN xml_node_iterator xml_node_iterator::operator--(int) - { - xml_node_iterator temp = *this; - --*this; - return temp; - } - - PUGI__FN xml_attribute_iterator::xml_attribute_iterator() - { - } - - PUGI__FN xml_attribute_iterator::xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent): _wrap(attr), _parent(parent) - { - } - - PUGI__FN xml_attribute_iterator::xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) - { - } - - PUGI__FN bool xml_attribute_iterator::operator==(const xml_attribute_iterator& rhs) const - { - return _wrap._attr == rhs._wrap._attr && _parent._root == rhs._parent._root; - } - - PUGI__FN bool xml_attribute_iterator::operator!=(const xml_attribute_iterator& rhs) const - { - return _wrap._attr != rhs._wrap._attr || _parent._root != rhs._parent._root; - } - - PUGI__FN xml_attribute& xml_attribute_iterator::operator*() const - { - assert(_wrap._attr); - return _wrap; - } - - PUGI__FN xml_attribute* xml_attribute_iterator::operator->() const - { - assert(_wrap._attr); - return const_cast(&_wrap); // BCC32 workaround - } - - PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator++() - { - assert(_wrap._attr); - _wrap._attr = _wrap._attr->next_attribute; - return *this; - } - - PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator++(int) - { - xml_attribute_iterator temp = *this; - ++*this; - return temp; - } - - PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator--() - { - _wrap = _wrap._attr ? _wrap.previous_attribute() : _parent.last_attribute(); - return *this; - } - - PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator--(int) - { - xml_attribute_iterator temp = *this; - --*this; - return temp; - } - - PUGI__FN xml_named_node_iterator::xml_named_node_iterator(): _name(0) - { - } - - PUGI__FN xml_named_node_iterator::xml_named_node_iterator(const xml_node& node, const char_t* name): _node(node), _name(name) - { - } - - PUGI__FN bool xml_named_node_iterator::operator==(const xml_named_node_iterator& rhs) const - { - return _node == rhs._node; - } - - PUGI__FN bool xml_named_node_iterator::operator!=(const xml_named_node_iterator& rhs) const - { - return _node != rhs._node; - } - - PUGI__FN xml_node& xml_named_node_iterator::operator*() const - { - assert(_node._root); - return _node; - } - - PUGI__FN xml_node* xml_named_node_iterator::operator->() const - { - assert(_node._root); - return const_cast(&_node); // BCC32 workaround - } - - PUGI__FN const xml_named_node_iterator& xml_named_node_iterator::operator++() - { - assert(_node._root); - _node = _node.next_sibling(_name); - return *this; - } - - PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator++(int) - { - xml_named_node_iterator temp = *this; - ++*this; - return temp; - } - - PUGI__FN xml_parse_result::xml_parse_result(): status(status_internal_error), offset(0), encoding(encoding_auto) - { - } - - PUGI__FN xml_parse_result::operator bool() const - { - return status == status_ok; - } - - PUGI__FN const char* xml_parse_result::description() const - { - switch (status) - { - case status_ok: return "No error"; - - case status_file_not_found: return "File was not found"; - case status_io_error: return "Error reading from file/stream"; - case status_out_of_memory: return "Could not allocate memory"; - case status_internal_error: return "Internal error occurred"; - - case status_unrecognized_tag: return "Could not determine tag type"; - - case status_bad_pi: return "Error parsing document declaration/processing instruction"; - case status_bad_comment: return "Error parsing comment"; - case status_bad_cdata: return "Error parsing CDATA section"; - case status_bad_doctype: return "Error parsing document type declaration"; - case status_bad_pcdata: return "Error parsing PCDATA section"; - case status_bad_start_element: return "Error parsing start element tag"; - case status_bad_attribute: return "Error parsing element attribute"; - case status_bad_end_element: return "Error parsing end element tag"; - case status_end_element_mismatch: return "Start-end tags mismatch"; - - default: return "Unknown error"; - } - } - - PUGI__FN xml_document::xml_document(): _buffer(0) - { - create(); - } - - PUGI__FN xml_document::~xml_document() - { - destroy(); - } - - PUGI__FN void xml_document::reset() - { - destroy(); - create(); - } - - PUGI__FN void xml_document::reset(const xml_document& proto) - { - reset(); - - for (xml_node cur = proto.first_child(); cur; cur = cur.next_sibling()) - append_copy(cur); - } - - PUGI__FN void xml_document::create() - { - // initialize sentinel page - PUGI__STATIC_ASSERT(offsetof(impl::xml_memory_page, data) + sizeof(impl::xml_document_struct) + impl::xml_memory_page_alignment <= sizeof(_memory)); - - // align upwards to page boundary - void* page_memory = reinterpret_cast((reinterpret_cast(_memory) + (impl::xml_memory_page_alignment - 1)) & ~(impl::xml_memory_page_alignment - 1)); - - // prepare page structure - impl::xml_memory_page* page = impl::xml_memory_page::construct(page_memory); - - page->busy_size = impl::xml_memory_page_size; - - // allocate new root - _root = new (page->data) impl::xml_document_struct(page); - _root->prev_sibling_c = _root; - - // setup sentinel page - page->allocator = static_cast(_root); - } - - PUGI__FN void xml_document::destroy() - { - // destroy static storage - if (_buffer) - { - impl::xml_memory::deallocate(_buffer); - _buffer = 0; - } - - // destroy dynamic storage, leave sentinel page (it's in static memory) - if (_root) - { - impl::xml_memory_page* root_page = reinterpret_cast(_root->header & impl::xml_memory_page_pointer_mask); - assert(root_page && !root_page->prev && !root_page->memory); - - // destroy all pages - for (impl::xml_memory_page* page = root_page->next; page; ) - { - impl::xml_memory_page* next = page->next; - - impl::xml_allocator::deallocate_page(page); - - page = next; - } - - // cleanup root page - root_page->allocator = 0; - root_page->next = 0; - root_page->busy_size = root_page->freed_size = 0; - - _root = 0; - } - } - -#ifndef PUGIXML_NO_STL - PUGI__FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options, xml_encoding encoding) - { - reset(); - - return impl::load_stream_impl(*this, stream, options, encoding); - } - - PUGI__FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options) - { - reset(); - - return impl::load_stream_impl(*this, stream, options, encoding_wchar); - } -#endif - - PUGI__FN xml_parse_result xml_document::load(const char_t* contents, unsigned int options) - { - // Force native encoding (skip autodetection) - #ifdef PUGIXML_WCHAR_MODE - xml_encoding encoding = encoding_wchar; - #else - xml_encoding encoding = encoding_utf8; - #endif - - return load_buffer(contents, impl::strlength(contents) * sizeof(char_t), options, encoding); - } - - PUGI__FN xml_parse_result xml_document::load_file(const char* path_, unsigned int options, xml_encoding encoding) - { - reset(); - - FILE* file = fopen(path_, "rb"); - - return impl::load_file_impl(*this, file, options, encoding); - } - - PUGI__FN xml_parse_result xml_document::load_file(const wchar_t* path_, unsigned int options, xml_encoding encoding) - { - reset(); - - FILE* file = impl::open_file_wide(path_, L"rb"); - - return impl::load_file_impl(*this, file, options, encoding); - } - - PUGI__FN xml_parse_result xml_document::load_buffer_impl(void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own) - { - reset(); - - // check input buffer - assert(contents || size == 0); - - // get actual encoding - xml_encoding buffer_encoding = impl::get_buffer_encoding(encoding, contents, size); - - // get private buffer - char_t* buffer = 0; - size_t length = 0; - - if (!impl::convert_buffer(buffer, length, buffer_encoding, contents, size, is_mutable)) return impl::make_parse_result(status_out_of_memory); - - // delete original buffer if we performed a conversion - if (own && buffer != contents && contents) impl::xml_memory::deallocate(contents); - - // parse - xml_parse_result res = impl::xml_parser::parse(buffer, length, _root, options); - - // remember encoding - res.encoding = buffer_encoding; - - // grab onto buffer if it's our buffer, user is responsible for deallocating contens himself - if (own || buffer != contents) _buffer = buffer; - - return res; - } - - PUGI__FN xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) - { - return load_buffer_impl(const_cast(contents), size, options, encoding, false, false); - } - - PUGI__FN xml_parse_result xml_document::load_buffer_inplace(void* contents, size_t size, unsigned int options, xml_encoding encoding) - { - return load_buffer_impl(contents, size, options, encoding, true, false); - } - - PUGI__FN xml_parse_result xml_document::load_buffer_inplace_own(void* contents, size_t size, unsigned int options, xml_encoding encoding) - { - return load_buffer_impl(contents, size, options, encoding, true, true); - } - - PUGI__FN void xml_document::save(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding) const - { - impl::xml_buffered_writer buffered_writer(writer, encoding); - - if ((flags & format_write_bom) && encoding != encoding_latin1) - { - // BOM always represents the codepoint U+FEFF, so just write it in native encoding - #ifdef PUGIXML_WCHAR_MODE - unsigned int bom = 0xfeff; - buffered_writer.write(static_cast(bom)); - #else - buffered_writer.write('\xef', '\xbb', '\xbf'); - #endif - } - - if (!(flags & format_no_declaration) && !impl::has_declaration(*this)) - { - buffered_writer.write(PUGIXML_TEXT("'); - if (!(flags & format_raw)) buffered_writer.write('\n'); - } - - impl::node_output(buffered_writer, *this, indent, flags, 0); - } - -#ifndef PUGIXML_NO_STL - PUGI__FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding) const - { - xml_writer_stream writer(stream); - - save(writer, indent, flags, encoding); - } - - PUGI__FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags) const - { - xml_writer_stream writer(stream); - - save(writer, indent, flags, encoding_wchar); - } -#endif - - PUGI__FN bool xml_document::save_file(const char* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const - { - FILE* file = fopen(path_, (flags & format_save_file_text) ? "w" : "wb"); - return impl::save_file_impl(*this, file, indent, flags, encoding); - } - - PUGI__FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const - { - FILE* file = impl::open_file_wide(path_, (flags & format_save_file_text) ? L"w" : L"wb"); - return impl::save_file_impl(*this, file, indent, flags, encoding); - } - - PUGI__FN xml_node xml_document::document_element() const - { - for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) - if ((i->header & impl::xml_memory_page_type_mask) + 1 == node_element) - return xml_node(i); - - return xml_node(); - } - -#ifndef PUGIXML_NO_STL - PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const wchar_t* str) - { - assert(str); - - return impl::as_utf8_impl(str, wcslen(str)); - } - - PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const std::basic_string& str) - { - return impl::as_utf8_impl(str.c_str(), str.size()); - } - - PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const char* str) - { - assert(str); - - return impl::as_wide_impl(str, strlen(str)); - } - - PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const std::string& str) - { - return impl::as_wide_impl(str.c_str(), str.size()); - } -#endif - - PUGI__FN void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate) - { - impl::xml_memory::allocate = allocate; - impl::xml_memory::deallocate = deallocate; - } - - PUGI__FN allocation_function PUGIXML_FUNCTION get_memory_allocation_function() - { - return impl::xml_memory::allocate; - } - - PUGI__FN deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function() - { - return impl::xml_memory::deallocate; - } -} - -#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) -namespace std -{ - // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) - PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_node_iterator&) - { - return std::bidirectional_iterator_tag(); - } - - PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_attribute_iterator&) - { - return std::bidirectional_iterator_tag(); - } - - PUGI__FN std::forward_iterator_tag _Iter_cat(const pugi::xml_named_node_iterator&) - { - return std::forward_iterator_tag(); - } -} -#endif - -#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) -namespace std -{ - // Workarounds for (non-standard) iterator category detection - PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_node_iterator&) - { - return std::bidirectional_iterator_tag(); - } - - PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_attribute_iterator&) - { - return std::bidirectional_iterator_tag(); - } - - PUGI__FN std::forward_iterator_tag __iterator_category(const pugi::xml_named_node_iterator&) - { - return std::forward_iterator_tag(); - } -} -#endif - -#ifndef PUGIXML_NO_XPATH - -// STL replacements -PUGI__NS_BEGIN - struct equal_to - { - template bool operator()(const T& lhs, const T& rhs) const - { - return lhs == rhs; - } - }; - - struct not_equal_to - { - template bool operator()(const T& lhs, const T& rhs) const - { - return lhs != rhs; - } - }; - - struct less - { - template bool operator()(const T& lhs, const T& rhs) const - { - return lhs < rhs; - } - }; - - struct less_equal - { - template bool operator()(const T& lhs, const T& rhs) const - { - return lhs <= rhs; - } - }; - - template void swap(T& lhs, T& rhs) - { - T temp = lhs; - lhs = rhs; - rhs = temp; - } - - template I min_element(I begin, I end, const Pred& pred) - { - I result = begin; - - for (I it = begin + 1; it != end; ++it) - if (pred(*it, *result)) - result = it; - - return result; - } - - template void reverse(I begin, I end) - { - while (begin + 1 < end) swap(*begin++, *--end); - } - - template I unique(I begin, I end) - { - // fast skip head - while (begin + 1 < end && *begin != *(begin + 1)) begin++; - - if (begin == end) return begin; - - // last written element - I write = begin++; - - // merge unique elements - while (begin != end) - { - if (*begin != *write) - *++write = *begin++; - else - begin++; - } - - // past-the-end (write points to live element) - return write + 1; - } - - template void copy_backwards(I begin, I end, I target) - { - while (begin != end) *--target = *--end; - } - - template void insertion_sort(I begin, I end, const Pred& pred, T*) - { - assert(begin != end); - - for (I it = begin + 1; it != end; ++it) - { - T val = *it; - - if (pred(val, *begin)) - { - // move to front - copy_backwards(begin, it, it + 1); - *begin = val; - } - else - { - I hole = it; - - // move hole backwards - while (pred(val, *(hole - 1))) - { - *hole = *(hole - 1); - hole--; - } - - // fill hole with element - *hole = val; - } - } - } - - // std variant for elements with == - template void partition(I begin, I middle, I end, const Pred& pred, I* out_eqbeg, I* out_eqend) - { - I eqbeg = middle, eqend = middle + 1; - - // expand equal range - while (eqbeg != begin && *(eqbeg - 1) == *eqbeg) --eqbeg; - while (eqend != end && *eqend == *eqbeg) ++eqend; - - // process outer elements - I ltend = eqbeg, gtbeg = eqend; - - for (;;) - { - // find the element from the right side that belongs to the left one - for (; gtbeg != end; ++gtbeg) - if (!pred(*eqbeg, *gtbeg)) - { - if (*gtbeg == *eqbeg) swap(*gtbeg, *eqend++); - else break; - } - - // find the element from the left side that belongs to the right one - for (; ltend != begin; --ltend) - if (!pred(*(ltend - 1), *eqbeg)) - { - if (*eqbeg == *(ltend - 1)) swap(*(ltend - 1), *--eqbeg); - else break; - } - - // scanned all elements - if (gtbeg == end && ltend == begin) - { - *out_eqbeg = eqbeg; - *out_eqend = eqend; - return; - } - - // make room for elements by moving equal area - if (gtbeg == end) - { - if (--ltend != --eqbeg) swap(*ltend, *eqbeg); - swap(*eqbeg, *--eqend); - } - else if (ltend == begin) - { - if (eqend != gtbeg) swap(*eqbeg, *eqend); - ++eqend; - swap(*gtbeg++, *eqbeg++); - } - else swap(*gtbeg++, *--ltend); - } - } - - template void median3(I first, I middle, I last, const Pred& pred) - { - if (pred(*middle, *first)) swap(*middle, *first); - if (pred(*last, *middle)) swap(*last, *middle); - if (pred(*middle, *first)) swap(*middle, *first); - } - - template void median(I first, I middle, I last, const Pred& pred) - { - if (last - first <= 40) - { - // median of three for small chunks - median3(first, middle, last, pred); - } - else - { - // median of nine - size_t step = (last - first + 1) / 8; - - median3(first, first + step, first + 2 * step, pred); - median3(middle - step, middle, middle + step, pred); - median3(last - 2 * step, last - step, last, pred); - median3(first + step, middle, last - step, pred); - } - } - - template void sort(I begin, I end, const Pred& pred) - { - // sort large chunks - while (end - begin > 32) - { - // find median element - I middle = begin + (end - begin) / 2; - median(begin, middle, end - 1, pred); - - // partition in three chunks (< = >) - I eqbeg, eqend; - partition(begin, middle, end, pred, &eqbeg, &eqend); - - // loop on larger half - if (eqbeg - begin > end - eqend) - { - sort(eqend, end, pred); - end = eqbeg; - } - else - { - sort(begin, eqbeg, pred); - begin = eqend; - } - } - - // insertion sort small chunk - if (begin != end) insertion_sort(begin, end, pred, &*begin); - } -PUGI__NS_END - -// Allocator used for AST and evaluation stacks -PUGI__NS_BEGIN - struct xpath_memory_block - { - xpath_memory_block* next; - - char data[ - #ifdef PUGIXML_MEMORY_XPATH_PAGE_SIZE - PUGIXML_MEMORY_XPATH_PAGE_SIZE - #else - 4096 - #endif - ]; - }; - - class xpath_allocator - { - xpath_memory_block* _root; - size_t _root_size; - - public: - #ifdef PUGIXML_NO_EXCEPTIONS - jmp_buf* error_handler; - #endif - - xpath_allocator(xpath_memory_block* root, size_t root_size = 0): _root(root), _root_size(root_size) - { - #ifdef PUGIXML_NO_EXCEPTIONS - error_handler = 0; - #endif - } - - void* allocate_nothrow(size_t size) - { - const size_t block_capacity = sizeof(_root->data); - - // align size so that we're able to store pointers in subsequent blocks - size = (size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); - - if (_root_size + size <= block_capacity) - { - void* buf = _root->data + _root_size; - _root_size += size; - return buf; - } - else - { - size_t block_data_size = (size > block_capacity) ? size : block_capacity; - size_t block_size = block_data_size + offsetof(xpath_memory_block, data); - - xpath_memory_block* block = static_cast(xml_memory::allocate(block_size)); - if (!block) return 0; - - block->next = _root; - - _root = block; - _root_size = size; - - return block->data; - } - } - - void* allocate(size_t size) - { - void* result = allocate_nothrow(size); - - if (!result) - { - #ifdef PUGIXML_NO_EXCEPTIONS - assert(error_handler); - longjmp(*error_handler, 1); - #else - throw std::bad_alloc(); - #endif - } - - return result; - } - - void* reallocate(void* ptr, size_t old_size, size_t new_size) - { - // align size so that we're able to store pointers in subsequent blocks - old_size = (old_size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); - new_size = (new_size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); - - // we can only reallocate the last object - assert(ptr == 0 || static_cast(ptr) + old_size == _root->data + _root_size); - - // adjust root size so that we have not allocated the object at all - bool only_object = (_root_size == old_size); - - if (ptr) _root_size -= old_size; - - // allocate a new version (this will obviously reuse the memory if possible) - void* result = allocate(new_size); - assert(result); - - // we have a new block - if (result != ptr && ptr) - { - // copy old data - assert(new_size > old_size); - memcpy(result, ptr, old_size); - - // free the previous page if it had no other objects - if (only_object) - { - assert(_root->data == result); - assert(_root->next); - - xpath_memory_block* next = _root->next->next; - - if (next) - { - // deallocate the whole page, unless it was the first one - xml_memory::deallocate(_root->next); - _root->next = next; - } - } - } - - return result; - } - - void revert(const xpath_allocator& state) - { - // free all new pages - xpath_memory_block* cur = _root; - - while (cur != state._root) - { - xpath_memory_block* next = cur->next; - - xml_memory::deallocate(cur); - - cur = next; - } - - // restore state - _root = state._root; - _root_size = state._root_size; - } - - void release() - { - xpath_memory_block* cur = _root; - assert(cur); - - while (cur->next) - { - xpath_memory_block* next = cur->next; - - xml_memory::deallocate(cur); - - cur = next; - } - } - }; - - struct xpath_allocator_capture - { - xpath_allocator_capture(xpath_allocator* alloc): _target(alloc), _state(*alloc) - { - } - - ~xpath_allocator_capture() - { - _target->revert(_state); - } - - xpath_allocator* _target; - xpath_allocator _state; - }; - - struct xpath_stack - { - xpath_allocator* result; - xpath_allocator* temp; - }; - - struct xpath_stack_data - { - xpath_memory_block blocks[2]; - xpath_allocator result; - xpath_allocator temp; - xpath_stack stack; - - #ifdef PUGIXML_NO_EXCEPTIONS - jmp_buf error_handler; - #endif - - xpath_stack_data(): result(blocks + 0), temp(blocks + 1) - { - blocks[0].next = blocks[1].next = 0; - - stack.result = &result; - stack.temp = &temp; - - #ifdef PUGIXML_NO_EXCEPTIONS - result.error_handler = temp.error_handler = &error_handler; - #endif - } - - ~xpath_stack_data() - { - result.release(); - temp.release(); - } - }; -PUGI__NS_END - -// String class -PUGI__NS_BEGIN - class xpath_string - { - const char_t* _buffer; - bool _uses_heap; - - static char_t* duplicate_string(const char_t* string, size_t length, xpath_allocator* alloc) - { - char_t* result = static_cast(alloc->allocate((length + 1) * sizeof(char_t))); - assert(result); - - memcpy(result, string, length * sizeof(char_t)); - result[length] = 0; - - return result; - } - - static char_t* duplicate_string(const char_t* string, xpath_allocator* alloc) - { - return duplicate_string(string, strlength(string), alloc); - } - - public: - xpath_string(): _buffer(PUGIXML_TEXT("")), _uses_heap(false) - { - } - - explicit xpath_string(const char_t* str, xpath_allocator* alloc) - { - bool empty_ = (*str == 0); - - _buffer = empty_ ? PUGIXML_TEXT("") : duplicate_string(str, alloc); - _uses_heap = !empty_; - } - - explicit xpath_string(const char_t* str, bool use_heap): _buffer(str), _uses_heap(use_heap) - { - } - - xpath_string(const char_t* begin, const char_t* end, xpath_allocator* alloc) - { - assert(begin <= end); - - bool empty_ = (begin == end); - - _buffer = empty_ ? PUGIXML_TEXT("") : duplicate_string(begin, static_cast(end - begin), alloc); - _uses_heap = !empty_; - } - - void append(const xpath_string& o, xpath_allocator* alloc) - { - // skip empty sources - if (!*o._buffer) return; - - // fast append for constant empty target and constant source - if (!*_buffer && !_uses_heap && !o._uses_heap) - { - _buffer = o._buffer; - } - else - { - // need to make heap copy - size_t target_length = strlength(_buffer); - size_t source_length = strlength(o._buffer); - size_t result_length = target_length + source_length; - - // allocate new buffer - char_t* result = static_cast(alloc->reallocate(_uses_heap ? const_cast(_buffer) : 0, (target_length + 1) * sizeof(char_t), (result_length + 1) * sizeof(char_t))); - assert(result); - - // append first string to the new buffer in case there was no reallocation - if (!_uses_heap) memcpy(result, _buffer, target_length * sizeof(char_t)); - - // append second string to the new buffer - memcpy(result + target_length, o._buffer, source_length * sizeof(char_t)); - result[result_length] = 0; - - // finalize - _buffer = result; - _uses_heap = true; - } - } - - const char_t* c_str() const - { - return _buffer; - } - - size_t length() const - { - return strlength(_buffer); - } - - char_t* data(xpath_allocator* alloc) - { - // make private heap copy - if (!_uses_heap) - { - _buffer = duplicate_string(_buffer, alloc); - _uses_heap = true; - } - - return const_cast(_buffer); - } - - bool empty() const - { - return *_buffer == 0; - } - - bool operator==(const xpath_string& o) const - { - return strequal(_buffer, o._buffer); - } - - bool operator!=(const xpath_string& o) const - { - return !strequal(_buffer, o._buffer); - } - - bool uses_heap() const - { - return _uses_heap; - } - }; - - PUGI__FN xpath_string xpath_string_const(const char_t* str) - { - return xpath_string(str, false); - } -PUGI__NS_END - -PUGI__NS_BEGIN - PUGI__FN bool starts_with(const char_t* string, const char_t* pattern) - { - while (*pattern && *string == *pattern) - { - string++; - pattern++; - } - - return *pattern == 0; - } - - PUGI__FN const char_t* find_char(const char_t* s, char_t c) - { - #ifdef PUGIXML_WCHAR_MODE - return wcschr(s, c); - #else - return strchr(s, c); - #endif - } - - PUGI__FN const char_t* find_substring(const char_t* s, const char_t* p) - { - #ifdef PUGIXML_WCHAR_MODE - // MSVC6 wcsstr bug workaround (if s is empty it always returns 0) - return (*p == 0) ? s : wcsstr(s, p); - #else - return strstr(s, p); - #endif - } - - // Converts symbol to lower case, if it is an ASCII one - PUGI__FN char_t tolower_ascii(char_t ch) - { - return static_cast(ch - 'A') < 26 ? static_cast(ch | ' ') : ch; - } - - PUGI__FN xpath_string string_value(const xpath_node& na, xpath_allocator* alloc) - { - if (na.attribute()) - return xpath_string_const(na.attribute().value()); - else - { - const xml_node& n = na.node(); - - switch (n.type()) - { - case node_pcdata: - case node_cdata: - case node_comment: - case node_pi: - return xpath_string_const(n.value()); - - case node_document: - case node_element: - { - xpath_string result; - - xml_node cur = n.first_child(); - - while (cur && cur != n) - { - if (cur.type() == node_pcdata || cur.type() == node_cdata) - result.append(xpath_string_const(cur.value()), alloc); - - if (cur.first_child()) - cur = cur.first_child(); - else if (cur.next_sibling()) - cur = cur.next_sibling(); - else - { - while (!cur.next_sibling() && cur != n) - cur = cur.parent(); - - if (cur != n) cur = cur.next_sibling(); - } - } - - return result; - } - - default: - return xpath_string(); - } - } - } - - PUGI__FN unsigned int node_height(xml_node n) - { - unsigned int result = 0; - - while (n) - { - ++result; - n = n.parent(); - } - - return result; - } - - PUGI__FN bool node_is_before(xml_node ln, unsigned int lh, xml_node rn, unsigned int rh) - { - // normalize heights - for (unsigned int i = rh; i < lh; i++) ln = ln.parent(); - for (unsigned int j = lh; j < rh; j++) rn = rn.parent(); - - // one node is the ancestor of the other - if (ln == rn) return lh < rh; - - // find common ancestor - while (ln.parent() != rn.parent()) - { - ln = ln.parent(); - rn = rn.parent(); - } - - // there is no common ancestor (the shared parent is null), nodes are from different documents - if (!ln.parent()) return ln < rn; - - // determine sibling order - for (; ln; ln = ln.next_sibling()) - if (ln == rn) - return true; - - return false; - } - - PUGI__FN bool node_is_ancestor(xml_node parent, xml_node node) - { - while (node && node != parent) node = node.parent(); - - return parent && node == parent; - } - - PUGI__FN const void* document_order(const xpath_node& xnode) - { - xml_node_struct* node = xnode.node().internal_object(); - - if (node) - { - if (node->name && (node->header & xml_memory_page_name_allocated_mask) == 0) return node->name; - if (node->value && (node->header & xml_memory_page_value_allocated_mask) == 0) return node->value; - return 0; - } - - xml_attribute_struct* attr = xnode.attribute().internal_object(); - - if (attr) - { - if ((attr->header & xml_memory_page_name_allocated_mask) == 0) return attr->name; - if ((attr->header & xml_memory_page_value_allocated_mask) == 0) return attr->value; - return 0; - } - - return 0; - } - - struct document_order_comparator - { - bool operator()(const xpath_node& lhs, const xpath_node& rhs) const - { - // optimized document order based check - const void* lo = document_order(lhs); - const void* ro = document_order(rhs); - - if (lo && ro) return lo < ro; - - // slow comparison - xml_node ln = lhs.node(), rn = rhs.node(); - - // compare attributes - if (lhs.attribute() && rhs.attribute()) - { - // shared parent - if (lhs.parent() == rhs.parent()) - { - // determine sibling order - for (xml_attribute a = lhs.attribute(); a; a = a.next_attribute()) - if (a == rhs.attribute()) - return true; - - return false; - } - - // compare attribute parents - ln = lhs.parent(); - rn = rhs.parent(); - } - else if (lhs.attribute()) - { - // attributes go after the parent element - if (lhs.parent() == rhs.node()) return false; - - ln = lhs.parent(); - } - else if (rhs.attribute()) - { - // attributes go after the parent element - if (rhs.parent() == lhs.node()) return true; - - rn = rhs.parent(); - } - - if (ln == rn) return false; - - unsigned int lh = node_height(ln); - unsigned int rh = node_height(rn); - - return node_is_before(ln, lh, rn, rh); - } - }; - - struct duplicate_comparator - { - bool operator()(const xpath_node& lhs, const xpath_node& rhs) const - { - if (lhs.attribute()) return rhs.attribute() ? lhs.attribute() < rhs.attribute() : true; - else return rhs.attribute() ? false : lhs.node() < rhs.node(); - } - }; - - PUGI__FN double gen_nan() - { - #if defined(__STDC_IEC_559__) || ((FLT_RADIX - 0 == 2) && (FLT_MAX_EXP - 0 == 128) && (FLT_MANT_DIG - 0 == 24)) - union { float f; uint32_t i; } u[sizeof(float) == sizeof(uint32_t) ? 1 : -1]; - u[0].i = 0x7fc00000; - return u[0].f; - #else - // fallback - const volatile double zero = 0.0; - return zero / zero; - #endif - } - - PUGI__FN bool is_nan(double value) - { - #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) - return !!_isnan(value); - #elif defined(fpclassify) && defined(FP_NAN) - return fpclassify(value) == FP_NAN; - #else - // fallback - const volatile double v = value; - return v != v; - #endif - } - - PUGI__FN const char_t* convert_number_to_string_special(double value) - { - #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) - if (_finite(value)) return (value == 0) ? PUGIXML_TEXT("0") : 0; - if (_isnan(value)) return PUGIXML_TEXT("NaN"); - return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); - #elif defined(fpclassify) && defined(FP_NAN) && defined(FP_INFINITE) && defined(FP_ZERO) - switch (fpclassify(value)) - { - case FP_NAN: - return PUGIXML_TEXT("NaN"); - - case FP_INFINITE: - return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); - - case FP_ZERO: - return PUGIXML_TEXT("0"); - - default: - return 0; - } - #else - // fallback - const volatile double v = value; - - if (v == 0) return PUGIXML_TEXT("0"); - if (v != v) return PUGIXML_TEXT("NaN"); - if (v * 2 == v) return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); - return 0; - #endif - } - - PUGI__FN bool convert_number_to_boolean(double value) - { - return (value != 0 && !is_nan(value)); - } - - PUGI__FN void truncate_zeros(char* begin, char* end) - { - while (begin != end && end[-1] == '0') end--; - - *end = 0; - } - - // gets mantissa digits in the form of 0.xxxxx with 0. implied and the exponent -#if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 && !defined(_WIN32_WCE) - PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent) - { - // get base values - int sign, exponent; - _ecvt_s(buffer, buffer_size, value, DBL_DIG + 1, &exponent, &sign); - - // truncate redundant zeros - truncate_zeros(buffer, buffer + strlen(buffer)); - - // fill results - *out_mantissa = buffer; - *out_exponent = exponent; - } -#else - PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent) - { - // get a scientific notation value with IEEE DBL_DIG decimals - sprintf(buffer, "%.*e", DBL_DIG, value); - assert(strlen(buffer) < buffer_size); - (void)!buffer_size; - - // get the exponent (possibly negative) - char* exponent_string = strchr(buffer, 'e'); - assert(exponent_string); - - int exponent = atoi(exponent_string + 1); - - // extract mantissa string: skip sign - char* mantissa = buffer[0] == '-' ? buffer + 1 : buffer; - assert(mantissa[0] != '0' && mantissa[1] == '.'); - - // divide mantissa by 10 to eliminate integer part - mantissa[1] = mantissa[0]; - mantissa++; - exponent++; - - // remove extra mantissa digits and zero-terminate mantissa - truncate_zeros(mantissa, exponent_string); - - // fill results - *out_mantissa = mantissa; - *out_exponent = exponent; - } -#endif - - PUGI__FN xpath_string convert_number_to_string(double value, xpath_allocator* alloc) - { - // try special number conversion - const char_t* special = convert_number_to_string_special(value); - if (special) return xpath_string_const(special); - - // get mantissa + exponent form - char mantissa_buffer[64]; - - char* mantissa; - int exponent; - convert_number_to_mantissa_exponent(value, mantissa_buffer, sizeof(mantissa_buffer), &mantissa, &exponent); - - // make the number! - char_t result[512]; - char_t* s = result; - - // sign - if (value < 0) *s++ = '-'; - - // integer part - if (exponent <= 0) - { - *s++ = '0'; - } - else - { - while (exponent > 0) - { - assert(*mantissa == 0 || static_cast(*mantissa - '0') <= 9); - *s++ = *mantissa ? *mantissa++ : '0'; - exponent--; - } - } - - // fractional part - if (*mantissa) - { - // decimal point - *s++ = '.'; - - // extra zeroes from negative exponent - while (exponent < 0) - { - *s++ = '0'; - exponent++; - } - - // extra mantissa digits - while (*mantissa) - { - assert(static_cast(*mantissa - '0') <= 9); - *s++ = *mantissa++; - } - } - - // zero-terminate - assert(s < result + sizeof(result) / sizeof(result[0])); - *s = 0; - - return xpath_string(result, alloc); - } - - PUGI__FN bool check_string_to_number_format(const char_t* string) - { - // parse leading whitespace - while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; - - // parse sign - if (*string == '-') ++string; - - if (!*string) return false; - - // if there is no integer part, there should be a decimal part with at least one digit - if (!PUGI__IS_CHARTYPEX(string[0], ctx_digit) && (string[0] != '.' || !PUGI__IS_CHARTYPEX(string[1], ctx_digit))) return false; - - // parse integer part - while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; - - // parse decimal part - if (*string == '.') - { - ++string; - - while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; - } - - // parse trailing whitespace - while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; - - return *string == 0; - } - - PUGI__FN double convert_string_to_number(const char_t* string) - { - // check string format - if (!check_string_to_number_format(string)) return gen_nan(); - - // parse string - #ifdef PUGIXML_WCHAR_MODE - return wcstod(string, 0); - #else - return atof(string); - #endif - } - - PUGI__FN bool convert_string_to_number(const char_t* begin, const char_t* end, double* out_result) - { - char_t buffer[32]; - - size_t length = static_cast(end - begin); - char_t* scratch = buffer; - - if (length >= sizeof(buffer) / sizeof(buffer[0])) - { - // need to make dummy on-heap copy - scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); - if (!scratch) return false; - } - - // copy string to zero-terminated buffer and perform conversion - memcpy(scratch, begin, length * sizeof(char_t)); - scratch[length] = 0; - - *out_result = convert_string_to_number(scratch); - - // free dummy buffer - if (scratch != buffer) xml_memory::deallocate(scratch); - - return true; - } - - PUGI__FN double round_nearest(double value) - { - return floor(value + 0.5); - } - - PUGI__FN double round_nearest_nzero(double value) - { - // same as round_nearest, but returns -0 for [-0.5, -0] - // ceil is used to differentiate between +0 and -0 (we return -0 for [-0.5, -0] and +0 for +0) - return (value >= -0.5 && value <= 0) ? ceil(value) : floor(value + 0.5); - } - - PUGI__FN const char_t* qualified_name(const xpath_node& node) - { - return node.attribute() ? node.attribute().name() : node.node().name(); - } - - PUGI__FN const char_t* local_name(const xpath_node& node) - { - const char_t* name = qualified_name(node); - const char_t* p = find_char(name, ':'); - - return p ? p + 1 : name; - } - - struct namespace_uri_predicate - { - const char_t* prefix; - size_t prefix_length; - - namespace_uri_predicate(const char_t* name) - { - const char_t* pos = find_char(name, ':'); - - prefix = pos ? name : 0; - prefix_length = pos ? static_cast(pos - name) : 0; - } - - bool operator()(const xml_attribute& a) const - { - const char_t* name = a.name(); - - if (!starts_with(name, PUGIXML_TEXT("xmlns"))) return false; - - return prefix ? name[5] == ':' && strequalrange(name + 6, prefix, prefix_length) : name[5] == 0; - } - }; - - PUGI__FN const char_t* namespace_uri(const xml_node& node) - { - namespace_uri_predicate pred = node.name(); - - xml_node p = node; - - while (p) - { - xml_attribute a = p.find_attribute(pred); - - if (a) return a.value(); - - p = p.parent(); - } - - return PUGIXML_TEXT(""); - } - - PUGI__FN const char_t* namespace_uri(const xml_attribute& attr, const xml_node& parent) - { - namespace_uri_predicate pred = attr.name(); - - // Default namespace does not apply to attributes - if (!pred.prefix) return PUGIXML_TEXT(""); - - xml_node p = parent; - - while (p) - { - xml_attribute a = p.find_attribute(pred); - - if (a) return a.value(); - - p = p.parent(); - } - - return PUGIXML_TEXT(""); - } - - PUGI__FN const char_t* namespace_uri(const xpath_node& node) - { - return node.attribute() ? namespace_uri(node.attribute(), node.parent()) : namespace_uri(node.node()); - } - - PUGI__FN void normalize_space(char_t* buffer) - { - char_t* write = buffer; - - for (char_t* it = buffer; *it; ) - { - char_t ch = *it++; - - if (PUGI__IS_CHARTYPE(ch, ct_space)) - { - // replace whitespace sequence with single space - while (PUGI__IS_CHARTYPE(*it, ct_space)) it++; - - // avoid leading spaces - if (write != buffer) *write++ = ' '; - } - else *write++ = ch; - } - - // remove trailing space - if (write != buffer && PUGI__IS_CHARTYPE(write[-1], ct_space)) write--; - - // zero-terminate - *write = 0; - } - - PUGI__FN void translate(char_t* buffer, const char_t* from, const char_t* to) - { - size_t to_length = strlength(to); - - char_t* write = buffer; - - while (*buffer) - { - PUGI__DMC_VOLATILE char_t ch = *buffer++; - - const char_t* pos = find_char(from, ch); - - if (!pos) - *write++ = ch; // do not process - else if (static_cast(pos - from) < to_length) - *write++ = to[pos - from]; // replace - } - - // zero-terminate - *write = 0; - } - - struct xpath_variable_boolean: xpath_variable - { - xpath_variable_boolean(): value(false) - { - } - - bool value; - char_t name[1]; - }; - - struct xpath_variable_number: xpath_variable - { - xpath_variable_number(): value(0) - { - } - - double value; - char_t name[1]; - }; - - struct xpath_variable_string: xpath_variable - { - xpath_variable_string(): value(0) - { - } - - ~xpath_variable_string() - { - if (value) xml_memory::deallocate(value); - } - - char_t* value; - char_t name[1]; - }; - - struct xpath_variable_node_set: xpath_variable - { - xpath_node_set value; - char_t name[1]; - }; - - static const xpath_node_set dummy_node_set; - - PUGI__FN unsigned int hash_string(const char_t* str) - { - // Jenkins one-at-a-time hash (http://en.wikipedia.org/wiki/Jenkins_hash_function#one-at-a-time) - unsigned int result = 0; - - while (*str) - { - result += static_cast(*str++); - result += result << 10; - result ^= result >> 6; - } - - result += result << 3; - result ^= result >> 11; - result += result << 15; - - return result; - } - - template PUGI__FN T* new_xpath_variable(const char_t* name) - { - size_t length = strlength(name); - if (length == 0) return 0; // empty variable names are invalid - - // $$ we can't use offsetof(T, name) because T is non-POD, so we just allocate additional length characters - void* memory = xml_memory::allocate(sizeof(T) + length * sizeof(char_t)); - if (!memory) return 0; - - T* result = new (memory) T(); - - memcpy(result->name, name, (length + 1) * sizeof(char_t)); - - return result; - } - - PUGI__FN xpath_variable* new_xpath_variable(xpath_value_type type, const char_t* name) - { - switch (type) - { - case xpath_type_node_set: - return new_xpath_variable(name); - - case xpath_type_number: - return new_xpath_variable(name); - - case xpath_type_string: - return new_xpath_variable(name); - - case xpath_type_boolean: - return new_xpath_variable(name); - - default: - return 0; - } - } - - template PUGI__FN void delete_xpath_variable(T* var) - { - var->~T(); - xml_memory::deallocate(var); - } - - PUGI__FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var) - { - switch (type) - { - case xpath_type_node_set: - delete_xpath_variable(static_cast(var)); - break; - - case xpath_type_number: - delete_xpath_variable(static_cast(var)); - break; - - case xpath_type_string: - delete_xpath_variable(static_cast(var)); - break; - - case xpath_type_boolean: - delete_xpath_variable(static_cast(var)); - break; - - default: - assert(!"Invalid variable type"); - } - } - - PUGI__FN xpath_variable* get_variable(xpath_variable_set* set, const char_t* begin, const char_t* end) - { - char_t buffer[32]; - - size_t length = static_cast(end - begin); - char_t* scratch = buffer; - - if (length >= sizeof(buffer) / sizeof(buffer[0])) - { - // need to make dummy on-heap copy - scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); - if (!scratch) return 0; - } - - // copy string to zero-terminated buffer and perform lookup - memcpy(scratch, begin, length * sizeof(char_t)); - scratch[length] = 0; - - xpath_variable* result = set->get(scratch); - - // free dummy buffer - if (scratch != buffer) xml_memory::deallocate(scratch); - - return result; - } -PUGI__NS_END - -// Internal node set class -PUGI__NS_BEGIN - PUGI__FN xpath_node_set::type_t xpath_sort(xpath_node* begin, xpath_node* end, xpath_node_set::type_t type, bool rev) - { - xpath_node_set::type_t order = rev ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted; - - if (type == xpath_node_set::type_unsorted) - { - sort(begin, end, document_order_comparator()); - - type = xpath_node_set::type_sorted; - } - - if (type != order) reverse(begin, end); - - return order; - } - - PUGI__FN xpath_node xpath_first(const xpath_node* begin, const xpath_node* end, xpath_node_set::type_t type) - { - if (begin == end) return xpath_node(); - - switch (type) - { - case xpath_node_set::type_sorted: - return *begin; - - case xpath_node_set::type_sorted_reverse: - return *(end - 1); - - case xpath_node_set::type_unsorted: - return *min_element(begin, end, document_order_comparator()); - - default: - assert(!"Invalid node set type"); - return xpath_node(); - } - } - - class xpath_node_set_raw - { - xpath_node_set::type_t _type; - - xpath_node* _begin; - xpath_node* _end; - xpath_node* _eos; - - public: - xpath_node_set_raw(): _type(xpath_node_set::type_unsorted), _begin(0), _end(0), _eos(0) - { - } - - xpath_node* begin() const - { - return _begin; - } - - xpath_node* end() const - { - return _end; - } - - bool empty() const - { - return _begin == _end; - } - - size_t size() const - { - return static_cast(_end - _begin); - } - - xpath_node first() const - { - return xpath_first(_begin, _end, _type); - } - - void push_back(const xpath_node& node, xpath_allocator* alloc) - { - if (_end == _eos) - { - size_t capacity = static_cast(_eos - _begin); - - // get new capacity (1.5x rule) - size_t new_capacity = capacity + capacity / 2 + 1; - - // reallocate the old array or allocate a new one - xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), new_capacity * sizeof(xpath_node))); - assert(data); - - // finalize - _begin = data; - _end = data + capacity; - _eos = data + new_capacity; - } - - *_end++ = node; - } - - void append(const xpath_node* begin_, const xpath_node* end_, xpath_allocator* alloc) - { - size_t size_ = static_cast(_end - _begin); - size_t capacity = static_cast(_eos - _begin); - size_t count = static_cast(end_ - begin_); - - if (size_ + count > capacity) - { - // reallocate the old array or allocate a new one - xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), (size_ + count) * sizeof(xpath_node))); - assert(data); - - // finalize - _begin = data; - _end = data + size_; - _eos = data + size_ + count; - } - - memcpy(_end, begin_, count * sizeof(xpath_node)); - _end += count; - } - - void sort_do() - { - _type = xpath_sort(_begin, _end, _type, false); - } - - void truncate(xpath_node* pos) - { - assert(_begin <= pos && pos <= _end); - - _end = pos; - } - - void remove_duplicates() - { - if (_type == xpath_node_set::type_unsorted) - sort(_begin, _end, duplicate_comparator()); - - _end = unique(_begin, _end); - } - - xpath_node_set::type_t type() const - { - return _type; - } - - void set_type(xpath_node_set::type_t value) - { - _type = value; - } - }; -PUGI__NS_END - -PUGI__NS_BEGIN - struct xpath_context - { - xpath_node n; - size_t position, size; - - xpath_context(const xpath_node& n_, size_t position_, size_t size_): n(n_), position(position_), size(size_) - { - } - }; - - enum lexeme_t - { - lex_none = 0, - lex_equal, - lex_not_equal, - lex_less, - lex_greater, - lex_less_or_equal, - lex_greater_or_equal, - lex_plus, - lex_minus, - lex_multiply, - lex_union, - lex_var_ref, - lex_open_brace, - lex_close_brace, - lex_quoted_string, - lex_number, - lex_slash, - lex_double_slash, - lex_open_square_brace, - lex_close_square_brace, - lex_string, - lex_comma, - lex_axis_attribute, - lex_dot, - lex_double_dot, - lex_double_colon, - lex_eof - }; - - struct xpath_lexer_string - { - const char_t* begin; - const char_t* end; - - xpath_lexer_string(): begin(0), end(0) - { - } - - bool operator==(const char_t* other) const - { - size_t length = static_cast(end - begin); - - return strequalrange(other, begin, length); - } - }; - - class xpath_lexer - { - const char_t* _cur; - const char_t* _cur_lexeme_pos; - xpath_lexer_string _cur_lexeme_contents; - - lexeme_t _cur_lexeme; - - public: - explicit xpath_lexer(const char_t* query): _cur(query) - { - next(); - } - - const char_t* state() const - { - return _cur; - } - - void next() - { - const char_t* cur = _cur; - - while (PUGI__IS_CHARTYPE(*cur, ct_space)) ++cur; - - // save lexeme position for error reporting - _cur_lexeme_pos = cur; - - switch (*cur) - { - case 0: - _cur_lexeme = lex_eof; - break; - - case '>': - if (*(cur+1) == '=') - { - cur += 2; - _cur_lexeme = lex_greater_or_equal; - } - else - { - cur += 1; - _cur_lexeme = lex_greater; - } - break; - - case '<': - if (*(cur+1) == '=') - { - cur += 2; - _cur_lexeme = lex_less_or_equal; - } - else - { - cur += 1; - _cur_lexeme = lex_less; - } - break; - - case '!': - if (*(cur+1) == '=') - { - cur += 2; - _cur_lexeme = lex_not_equal; - } - else - { - _cur_lexeme = lex_none; - } - break; - - case '=': - cur += 1; - _cur_lexeme = lex_equal; - - break; - - case '+': - cur += 1; - _cur_lexeme = lex_plus; - - break; - - case '-': - cur += 1; - _cur_lexeme = lex_minus; - - break; - - case '*': - cur += 1; - _cur_lexeme = lex_multiply; - - break; - - case '|': - cur += 1; - _cur_lexeme = lex_union; - - break; - - case '$': - cur += 1; - - if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) - { - _cur_lexeme_contents.begin = cur; - - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; - - if (cur[0] == ':' && PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // qname - { - cur++; // : - - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; - } - - _cur_lexeme_contents.end = cur; - - _cur_lexeme = lex_var_ref; - } - else - { - _cur_lexeme = lex_none; - } - - break; - - case '(': - cur += 1; - _cur_lexeme = lex_open_brace; - - break; - - case ')': - cur += 1; - _cur_lexeme = lex_close_brace; - - break; - - case '[': - cur += 1; - _cur_lexeme = lex_open_square_brace; - - break; - - case ']': - cur += 1; - _cur_lexeme = lex_close_square_brace; - - break; - - case ',': - cur += 1; - _cur_lexeme = lex_comma; - - break; - - case '/': - if (*(cur+1) == '/') - { - cur += 2; - _cur_lexeme = lex_double_slash; - } - else - { - cur += 1; - _cur_lexeme = lex_slash; - } - break; - - case '.': - if (*(cur+1) == '.') - { - cur += 2; - _cur_lexeme = lex_double_dot; - } - else if (PUGI__IS_CHARTYPEX(*(cur+1), ctx_digit)) - { - _cur_lexeme_contents.begin = cur; // . - - ++cur; - - while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; - - _cur_lexeme_contents.end = cur; - - _cur_lexeme = lex_number; - } - else - { - cur += 1; - _cur_lexeme = lex_dot; - } - break; - - case '@': - cur += 1; - _cur_lexeme = lex_axis_attribute; - - break; - - case '"': - case '\'': - { - char_t terminator = *cur; - - ++cur; - - _cur_lexeme_contents.begin = cur; - while (*cur && *cur != terminator) cur++; - _cur_lexeme_contents.end = cur; - - if (!*cur) - _cur_lexeme = lex_none; - else - { - cur += 1; - _cur_lexeme = lex_quoted_string; - } - - break; - } - - case ':': - if (*(cur+1) == ':') - { - cur += 2; - _cur_lexeme = lex_double_colon; - } - else - { - _cur_lexeme = lex_none; - } - break; - - default: - if (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) - { - _cur_lexeme_contents.begin = cur; - - while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; - - if (*cur == '.') - { - cur++; - - while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; - } - - _cur_lexeme_contents.end = cur; - - _cur_lexeme = lex_number; - } - else if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) - { - _cur_lexeme_contents.begin = cur; - - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; - - if (cur[0] == ':') - { - if (cur[1] == '*') // namespace test ncname:* - { - cur += 2; // :* - } - else if (PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // namespace test qname - { - cur++; // : - - while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; - } - } - - _cur_lexeme_contents.end = cur; - - _cur_lexeme = lex_string; - } - else - { - _cur_lexeme = lex_none; - } - } - - _cur = cur; - } - - lexeme_t current() const - { - return _cur_lexeme; - } - - const char_t* current_pos() const - { - return _cur_lexeme_pos; - } - - const xpath_lexer_string& contents() const - { - assert(_cur_lexeme == lex_var_ref || _cur_lexeme == lex_number || _cur_lexeme == lex_string || _cur_lexeme == lex_quoted_string); - - return _cur_lexeme_contents; - } - }; - - enum ast_type_t - { - ast_op_or, // left or right - ast_op_and, // left and right - ast_op_equal, // left = right - ast_op_not_equal, // left != right - ast_op_less, // left < right - ast_op_greater, // left > right - ast_op_less_or_equal, // left <= right - ast_op_greater_or_equal, // left >= right - ast_op_add, // left + right - ast_op_subtract, // left - right - ast_op_multiply, // left * right - ast_op_divide, // left / right - ast_op_mod, // left % right - ast_op_negate, // left - right - ast_op_union, // left | right - ast_predicate, // apply predicate to set; next points to next predicate - ast_filter, // select * from left where right - ast_filter_posinv, // select * from left where right; proximity position invariant - ast_string_constant, // string constant - ast_number_constant, // number constant - ast_variable, // variable - ast_func_last, // last() - ast_func_position, // position() - ast_func_count, // count(left) - ast_func_id, // id(left) - ast_func_local_name_0, // local-name() - ast_func_local_name_1, // local-name(left) - ast_func_namespace_uri_0, // namespace-uri() - ast_func_namespace_uri_1, // namespace-uri(left) - ast_func_name_0, // name() - ast_func_name_1, // name(left) - ast_func_string_0, // string() - ast_func_string_1, // string(left) - ast_func_concat, // concat(left, right, siblings) - ast_func_starts_with, // starts_with(left, right) - ast_func_contains, // contains(left, right) - ast_func_substring_before, // substring-before(left, right) - ast_func_substring_after, // substring-after(left, right) - ast_func_substring_2, // substring(left, right) - ast_func_substring_3, // substring(left, right, third) - ast_func_string_length_0, // string-length() - ast_func_string_length_1, // string-length(left) - ast_func_normalize_space_0, // normalize-space() - ast_func_normalize_space_1, // normalize-space(left) - ast_func_translate, // translate(left, right, third) - ast_func_boolean, // boolean(left) - ast_func_not, // not(left) - ast_func_true, // true() - ast_func_false, // false() - ast_func_lang, // lang(left) - ast_func_number_0, // number() - ast_func_number_1, // number(left) - ast_func_sum, // sum(left) - ast_func_floor, // floor(left) - ast_func_ceiling, // ceiling(left) - ast_func_round, // round(left) - ast_step, // process set left with step - ast_step_root // select root node - }; - - enum axis_t - { - axis_ancestor, - axis_ancestor_or_self, - axis_attribute, - axis_child, - axis_descendant, - axis_descendant_or_self, - axis_following, - axis_following_sibling, - axis_namespace, - axis_parent, - axis_preceding, - axis_preceding_sibling, - axis_self - }; - - enum nodetest_t - { - nodetest_none, - nodetest_name, - nodetest_type_node, - nodetest_type_comment, - nodetest_type_pi, - nodetest_type_text, - nodetest_pi, - nodetest_all, - nodetest_all_in_namespace - }; - - template struct axis_to_type - { - static const axis_t axis; - }; - - template const axis_t axis_to_type::axis = N; - - class xpath_ast_node - { - private: - // node type - char _type; - char _rettype; - - // for ast_step / ast_predicate - char _axis; - char _test; - - // tree node structure - xpath_ast_node* _left; - xpath_ast_node* _right; - xpath_ast_node* _next; - - union - { - // value for ast_string_constant - const char_t* string; - // value for ast_number_constant - double number; - // variable for ast_variable - xpath_variable* variable; - // node test for ast_step (node name/namespace/node type/pi target) - const char_t* nodetest; - } _data; - - xpath_ast_node(const xpath_ast_node&); - xpath_ast_node& operator=(const xpath_ast_node&); - - template static bool compare_eq(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) - { - xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); - - if (lt != xpath_type_node_set && rt != xpath_type_node_set) - { - if (lt == xpath_type_boolean || rt == xpath_type_boolean) - return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); - else if (lt == xpath_type_number || rt == xpath_type_number) - return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); - else if (lt == xpath_type_string || rt == xpath_type_string) - { - xpath_allocator_capture cr(stack.result); - - xpath_string ls = lhs->eval_string(c, stack); - xpath_string rs = rhs->eval_string(c, stack); - - return comp(ls, rs); - } - } - else if (lt == xpath_type_node_set && rt == xpath_type_node_set) - { - xpath_allocator_capture cr(stack.result); - - xpath_node_set_raw ls = lhs->eval_node_set(c, stack); - xpath_node_set_raw rs = rhs->eval_node_set(c, stack); - - for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) - for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) - { - xpath_allocator_capture cri(stack.result); - - if (comp(string_value(*li, stack.result), string_value(*ri, stack.result))) - return true; - } - - return false; - } - else - { - if (lt == xpath_type_node_set) - { - swap(lhs, rhs); - swap(lt, rt); - } - - if (lt == xpath_type_boolean) - return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); - else if (lt == xpath_type_number) - { - xpath_allocator_capture cr(stack.result); - - double l = lhs->eval_number(c, stack); - xpath_node_set_raw rs = rhs->eval_node_set(c, stack); - - for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) - { - xpath_allocator_capture cri(stack.result); - - if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) - return true; - } - - return false; - } - else if (lt == xpath_type_string) - { - xpath_allocator_capture cr(stack.result); - - xpath_string l = lhs->eval_string(c, stack); - xpath_node_set_raw rs = rhs->eval_node_set(c, stack); - - for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) - { - xpath_allocator_capture cri(stack.result); - - if (comp(l, string_value(*ri, stack.result))) - return true; - } - - return false; - } - } - - assert(!"Wrong types"); - return false; - } - - template static bool compare_rel(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) - { - xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); - - if (lt != xpath_type_node_set && rt != xpath_type_node_set) - return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); - else if (lt == xpath_type_node_set && rt == xpath_type_node_set) - { - xpath_allocator_capture cr(stack.result); - - xpath_node_set_raw ls = lhs->eval_node_set(c, stack); - xpath_node_set_raw rs = rhs->eval_node_set(c, stack); - - for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) - { - xpath_allocator_capture cri(stack.result); - - double l = convert_string_to_number(string_value(*li, stack.result).c_str()); - - for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) - { - xpath_allocator_capture crii(stack.result); - - if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) - return true; - } - } - - return false; - } - else if (lt != xpath_type_node_set && rt == xpath_type_node_set) - { - xpath_allocator_capture cr(stack.result); - - double l = lhs->eval_number(c, stack); - xpath_node_set_raw rs = rhs->eval_node_set(c, stack); - - for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) - { - xpath_allocator_capture cri(stack.result); - - if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) - return true; - } - - return false; - } - else if (lt == xpath_type_node_set && rt != xpath_type_node_set) - { - xpath_allocator_capture cr(stack.result); - - xpath_node_set_raw ls = lhs->eval_node_set(c, stack); - double r = rhs->eval_number(c, stack); - - for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) - { - xpath_allocator_capture cri(stack.result); - - if (comp(convert_string_to_number(string_value(*li, stack.result).c_str()), r)) - return true; - } - - return false; - } - else - { - assert(!"Wrong types"); - return false; - } - } - - void apply_predicate(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack) - { - assert(ns.size() >= first); - - size_t i = 1; - size_t size = ns.size() - first; - - xpath_node* last = ns.begin() + first; - - // remove_if... or well, sort of - for (xpath_node* it = last; it != ns.end(); ++it, ++i) - { - xpath_context c(*it, i, size); - - if (expr->rettype() == xpath_type_number) - { - if (expr->eval_number(c, stack) == i) - *last++ = *it; - } - else if (expr->eval_boolean(c, stack)) - *last++ = *it; - } - - ns.truncate(last); - } - - void apply_predicates(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack) - { - if (ns.size() == first) return; - - for (xpath_ast_node* pred = _right; pred; pred = pred->_next) - { - apply_predicate(ns, first, pred->_left, stack); - } - } - - void step_push(xpath_node_set_raw& ns, const xml_attribute& a, const xml_node& parent, xpath_allocator* alloc) - { - if (!a) return; - - const char_t* name = a.name(); - - // There are no attribute nodes corresponding to attributes that declare namespaces - // That is, "xmlns:..." or "xmlns" - if (starts_with(name, PUGIXML_TEXT("xmlns")) && (name[5] == 0 || name[5] == ':')) return; - - switch (_test) - { - case nodetest_name: - if (strequal(name, _data.nodetest)) ns.push_back(xpath_node(a, parent), alloc); - break; - - case nodetest_type_node: - case nodetest_all: - ns.push_back(xpath_node(a, parent), alloc); - break; - - case nodetest_all_in_namespace: - if (starts_with(name, _data.nodetest)) - ns.push_back(xpath_node(a, parent), alloc); - break; - - default: - ; - } - } - - void step_push(xpath_node_set_raw& ns, const xml_node& n, xpath_allocator* alloc) - { - if (!n) return; - - switch (_test) - { - case nodetest_name: - if (n.type() == node_element && strequal(n.name(), _data.nodetest)) ns.push_back(n, alloc); - break; - - case nodetest_type_node: - ns.push_back(n, alloc); - break; - - case nodetest_type_comment: - if (n.type() == node_comment) - ns.push_back(n, alloc); - break; - - case nodetest_type_text: - if (n.type() == node_pcdata || n.type() == node_cdata) - ns.push_back(n, alloc); - break; - - case nodetest_type_pi: - if (n.type() == node_pi) - ns.push_back(n, alloc); - break; - - case nodetest_pi: - if (n.type() == node_pi && strequal(n.name(), _data.nodetest)) - ns.push_back(n, alloc); - break; - - case nodetest_all: - if (n.type() == node_element) - ns.push_back(n, alloc); - break; - - case nodetest_all_in_namespace: - if (n.type() == node_element && starts_with(n.name(), _data.nodetest)) - ns.push_back(n, alloc); - break; - - default: - assert(!"Unknown axis"); - } - } - - template void step_fill(xpath_node_set_raw& ns, const xml_node& n, xpath_allocator* alloc, T) - { - const axis_t axis = T::axis; - - switch (axis) - { - case axis_attribute: - { - for (xml_attribute a = n.first_attribute(); a; a = a.next_attribute()) - step_push(ns, a, n, alloc); - - break; - } - - case axis_child: - { - for (xml_node c = n.first_child(); c; c = c.next_sibling()) - step_push(ns, c, alloc); - - break; - } - - case axis_descendant: - case axis_descendant_or_self: - { - if (axis == axis_descendant_or_self) - step_push(ns, n, alloc); - - xml_node cur = n.first_child(); - - while (cur && cur != n) - { - step_push(ns, cur, alloc); - - if (cur.first_child()) - cur = cur.first_child(); - else if (cur.next_sibling()) - cur = cur.next_sibling(); - else - { - while (!cur.next_sibling() && cur != n) - cur = cur.parent(); - - if (cur != n) cur = cur.next_sibling(); - } - } - - break; - } - - case axis_following_sibling: - { - for (xml_node c = n.next_sibling(); c; c = c.next_sibling()) - step_push(ns, c, alloc); - - break; - } - - case axis_preceding_sibling: - { - for (xml_node c = n.previous_sibling(); c; c = c.previous_sibling()) - step_push(ns, c, alloc); - - break; - } - - case axis_following: - { - xml_node cur = n; - - // exit from this node so that we don't include descendants - while (cur && !cur.next_sibling()) cur = cur.parent(); - cur = cur.next_sibling(); - - for (;;) - { - step_push(ns, cur, alloc); - - if (cur.first_child()) - cur = cur.first_child(); - else if (cur.next_sibling()) - cur = cur.next_sibling(); - else - { - while (cur && !cur.next_sibling()) cur = cur.parent(); - cur = cur.next_sibling(); - - if (!cur) break; - } - } - - break; - } - - case axis_preceding: - { - xml_node cur = n; - - while (cur && !cur.previous_sibling()) cur = cur.parent(); - cur = cur.previous_sibling(); - - for (;;) - { - if (cur.last_child()) - cur = cur.last_child(); - else - { - // leaf node, can't be ancestor - step_push(ns, cur, alloc); - - if (cur.previous_sibling()) - cur = cur.previous_sibling(); - else - { - do - { - cur = cur.parent(); - if (!cur) break; - - if (!node_is_ancestor(cur, n)) step_push(ns, cur, alloc); - } - while (!cur.previous_sibling()); - - cur = cur.previous_sibling(); - - if (!cur) break; - } - } - } - - break; - } - - case axis_ancestor: - case axis_ancestor_or_self: - { - if (axis == axis_ancestor_or_self) - step_push(ns, n, alloc); - - xml_node cur = n.parent(); - - while (cur) - { - step_push(ns, cur, alloc); - - cur = cur.parent(); - } - - break; - } - - case axis_self: - { - step_push(ns, n, alloc); - - break; - } - - case axis_parent: - { - if (n.parent()) step_push(ns, n.parent(), alloc); - - break; - } - - default: - assert(!"Unimplemented axis"); - } - } - - template void step_fill(xpath_node_set_raw& ns, const xml_attribute& a, const xml_node& p, xpath_allocator* alloc, T v) - { - const axis_t axis = T::axis; - - switch (axis) - { - case axis_ancestor: - case axis_ancestor_or_self: - { - if (axis == axis_ancestor_or_self && _test == nodetest_type_node) // reject attributes based on principal node type test - step_push(ns, a, p, alloc); - - xml_node cur = p; - - while (cur) - { - step_push(ns, cur, alloc); - - cur = cur.parent(); - } - - break; - } - - case axis_descendant_or_self: - case axis_self: - { - if (_test == nodetest_type_node) // reject attributes based on principal node type test - step_push(ns, a, p, alloc); - - break; - } - - case axis_following: - { - xml_node cur = p; - - for (;;) - { - if (cur.first_child()) - cur = cur.first_child(); - else if (cur.next_sibling()) - cur = cur.next_sibling(); - else - { - while (cur && !cur.next_sibling()) cur = cur.parent(); - cur = cur.next_sibling(); - - if (!cur) break; - } - - step_push(ns, cur, alloc); - } - - break; - } - - case axis_parent: - { - step_push(ns, p, alloc); - - break; - } - - case axis_preceding: - { - // preceding:: axis does not include attribute nodes and attribute ancestors (they are the same as parent's ancestors), so we can reuse node preceding - step_fill(ns, p, alloc, v); - break; - } - - default: - assert(!"Unimplemented axis"); - } - } - - template xpath_node_set_raw step_do(const xpath_context& c, const xpath_stack& stack, T v) - { - const axis_t axis = T::axis; - bool attributes = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_descendant_or_self || axis == axis_following || axis == axis_parent || axis == axis_preceding || axis == axis_self); - - xpath_node_set_raw ns; - ns.set_type((axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_preceding || axis == axis_preceding_sibling) ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted); - - if (_left) - { - xpath_node_set_raw s = _left->eval_node_set(c, stack); - - // self axis preserves the original order - if (axis == axis_self) ns.set_type(s.type()); - - for (const xpath_node* it = s.begin(); it != s.end(); ++it) - { - size_t size = ns.size(); - - // in general, all axes generate elements in a particular order, but there is no order guarantee if axis is applied to two nodes - if (axis != axis_self && size != 0) ns.set_type(xpath_node_set::type_unsorted); - - if (it->node()) - step_fill(ns, it->node(), stack.result, v); - else if (attributes) - step_fill(ns, it->attribute(), it->parent(), stack.result, v); - - apply_predicates(ns, size, stack); - } - } - else - { - if (c.n.node()) - step_fill(ns, c.n.node(), stack.result, v); - else if (attributes) - step_fill(ns, c.n.attribute(), c.n.parent(), stack.result, v); - - apply_predicates(ns, 0, stack); - } - - // child, attribute and self axes always generate unique set of nodes - // for other axis, if the set stayed sorted, it stayed unique because the traversal algorithms do not visit the same node twice - if (axis != axis_child && axis != axis_attribute && axis != axis_self && ns.type() == xpath_node_set::type_unsorted) - ns.remove_duplicates(); - - return ns; - } - - public: - xpath_ast_node(ast_type_t type, xpath_value_type rettype_, const char_t* value): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) - { - assert(type == ast_string_constant); - _data.string = value; - } - - xpath_ast_node(ast_type_t type, xpath_value_type rettype_, double value): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) - { - assert(type == ast_number_constant); - _data.number = value; - } - - xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_variable* value): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) - { - assert(type == ast_variable); - _data.variable = value; - } - - xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_ast_node* left = 0, xpath_ast_node* right = 0): - _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(left), _right(right), _next(0) - { - } - - xpath_ast_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents): - _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(static_cast(axis)), _test(static_cast(test)), _left(left), _right(0), _next(0) - { - _data.nodetest = contents; - } - - void set_next(xpath_ast_node* value) - { - _next = value; - } - - void set_right(xpath_ast_node* value) - { - _right = value; - } - - bool eval_boolean(const xpath_context& c, const xpath_stack& stack) - { - switch (_type) - { - case ast_op_or: - return _left->eval_boolean(c, stack) || _right->eval_boolean(c, stack); - - case ast_op_and: - return _left->eval_boolean(c, stack) && _right->eval_boolean(c, stack); - - case ast_op_equal: - return compare_eq(_left, _right, c, stack, equal_to()); - - case ast_op_not_equal: - return compare_eq(_left, _right, c, stack, not_equal_to()); - - case ast_op_less: - return compare_rel(_left, _right, c, stack, less()); - - case ast_op_greater: - return compare_rel(_right, _left, c, stack, less()); - - case ast_op_less_or_equal: - return compare_rel(_left, _right, c, stack, less_equal()); - - case ast_op_greater_or_equal: - return compare_rel(_right, _left, c, stack, less_equal()); - - case ast_func_starts_with: - { - xpath_allocator_capture cr(stack.result); - - xpath_string lr = _left->eval_string(c, stack); - xpath_string rr = _right->eval_string(c, stack); - - return starts_with(lr.c_str(), rr.c_str()); - } - - case ast_func_contains: - { - xpath_allocator_capture cr(stack.result); - - xpath_string lr = _left->eval_string(c, stack); - xpath_string rr = _right->eval_string(c, stack); - - return find_substring(lr.c_str(), rr.c_str()) != 0; - } - - case ast_func_boolean: - return _left->eval_boolean(c, stack); - - case ast_func_not: - return !_left->eval_boolean(c, stack); - - case ast_func_true: - return true; - - case ast_func_false: - return false; - - case ast_func_lang: - { - if (c.n.attribute()) return false; - - xpath_allocator_capture cr(stack.result); - - xpath_string lang = _left->eval_string(c, stack); - - for (xml_node n = c.n.node(); n; n = n.parent()) - { - xml_attribute a = n.attribute(PUGIXML_TEXT("xml:lang")); - - if (a) - { - const char_t* value = a.value(); - - // strnicmp / strncasecmp is not portable - for (const char_t* lit = lang.c_str(); *lit; ++lit) - { - if (tolower_ascii(*lit) != tolower_ascii(*value)) return false; - ++value; - } - - return *value == 0 || *value == '-'; - } - } - - return false; - } - - case ast_variable: - { - assert(_rettype == _data.variable->type()); - - if (_rettype == xpath_type_boolean) - return _data.variable->get_boolean(); - - // fallthrough to type conversion - } - - default: - { - switch (_rettype) - { - case xpath_type_number: - return convert_number_to_boolean(eval_number(c, stack)); - - case xpath_type_string: - { - xpath_allocator_capture cr(stack.result); - - return !eval_string(c, stack).empty(); - } - - case xpath_type_node_set: - { - xpath_allocator_capture cr(stack.result); - - return !eval_node_set(c, stack).empty(); - } - - default: - assert(!"Wrong expression for return type boolean"); - return false; - } - } - } - } - - double eval_number(const xpath_context& c, const xpath_stack& stack) - { - switch (_type) - { - case ast_op_add: - return _left->eval_number(c, stack) + _right->eval_number(c, stack); - - case ast_op_subtract: - return _left->eval_number(c, stack) - _right->eval_number(c, stack); - - case ast_op_multiply: - return _left->eval_number(c, stack) * _right->eval_number(c, stack); - - case ast_op_divide: - return _left->eval_number(c, stack) / _right->eval_number(c, stack); - - case ast_op_mod: - return fmod(_left->eval_number(c, stack), _right->eval_number(c, stack)); - - case ast_op_negate: - return -_left->eval_number(c, stack); - - case ast_number_constant: - return _data.number; - - case ast_func_last: - return static_cast(c.size); - - case ast_func_position: - return static_cast(c.position); - - case ast_func_count: - { - xpath_allocator_capture cr(stack.result); - - return static_cast(_left->eval_node_set(c, stack).size()); - } - - case ast_func_string_length_0: - { - xpath_allocator_capture cr(stack.result); - - return static_cast(string_value(c.n, stack.result).length()); - } - - case ast_func_string_length_1: - { - xpath_allocator_capture cr(stack.result); - - return static_cast(_left->eval_string(c, stack).length()); - } - - case ast_func_number_0: - { - xpath_allocator_capture cr(stack.result); - - return convert_string_to_number(string_value(c.n, stack.result).c_str()); - } - - case ast_func_number_1: - return _left->eval_number(c, stack); - - case ast_func_sum: - { - xpath_allocator_capture cr(stack.result); - - double r = 0; - - xpath_node_set_raw ns = _left->eval_node_set(c, stack); - - for (const xpath_node* it = ns.begin(); it != ns.end(); ++it) - { - xpath_allocator_capture cri(stack.result); - - r += convert_string_to_number(string_value(*it, stack.result).c_str()); - } - - return r; - } - - case ast_func_floor: - { - double r = _left->eval_number(c, stack); - - return r == r ? floor(r) : r; - } - - case ast_func_ceiling: - { - double r = _left->eval_number(c, stack); - - return r == r ? ceil(r) : r; - } - - case ast_func_round: - return round_nearest_nzero(_left->eval_number(c, stack)); - - case ast_variable: - { - assert(_rettype == _data.variable->type()); - - if (_rettype == xpath_type_number) - return _data.variable->get_number(); - - // fallthrough to type conversion - } - - default: - { - switch (_rettype) - { - case xpath_type_boolean: - return eval_boolean(c, stack) ? 1 : 0; - - case xpath_type_string: - { - xpath_allocator_capture cr(stack.result); - - return convert_string_to_number(eval_string(c, stack).c_str()); - } - - case xpath_type_node_set: - { - xpath_allocator_capture cr(stack.result); - - return convert_string_to_number(eval_string(c, stack).c_str()); - } - - default: - assert(!"Wrong expression for return type number"); - return 0; - } - - } - } - } - - xpath_string eval_string_concat(const xpath_context& c, const xpath_stack& stack) - { - assert(_type == ast_func_concat); - - xpath_allocator_capture ct(stack.temp); - - // count the string number - size_t count = 1; - for (xpath_ast_node* nc = _right; nc; nc = nc->_next) count++; - - // gather all strings - xpath_string static_buffer[4]; - xpath_string* buffer = static_buffer; - - // allocate on-heap for large concats - if (count > sizeof(static_buffer) / sizeof(static_buffer[0])) - { - buffer = static_cast(stack.temp->allocate(count * sizeof(xpath_string))); - assert(buffer); - } - - // evaluate all strings to temporary stack - xpath_stack swapped_stack = {stack.temp, stack.result}; - - buffer[0] = _left->eval_string(c, swapped_stack); - - size_t pos = 1; - for (xpath_ast_node* n = _right; n; n = n->_next, ++pos) buffer[pos] = n->eval_string(c, swapped_stack); - assert(pos == count); - - // get total length - size_t length = 0; - for (size_t i = 0; i < count; ++i) length += buffer[i].length(); - - // create final string - char_t* result = static_cast(stack.result->allocate((length + 1) * sizeof(char_t))); - assert(result); - - char_t* ri = result; - - for (size_t j = 0; j < count; ++j) - for (const char_t* bi = buffer[j].c_str(); *bi; ++bi) - *ri++ = *bi; - - *ri = 0; - - return xpath_string(result, true); - } - - xpath_string eval_string(const xpath_context& c, const xpath_stack& stack) - { - switch (_type) - { - case ast_string_constant: - return xpath_string_const(_data.string); - - case ast_func_local_name_0: - { - xpath_node na = c.n; - - return xpath_string_const(local_name(na)); - } - - case ast_func_local_name_1: - { - xpath_allocator_capture cr(stack.result); - - xpath_node_set_raw ns = _left->eval_node_set(c, stack); - xpath_node na = ns.first(); - - return xpath_string_const(local_name(na)); - } - - case ast_func_name_0: - { - xpath_node na = c.n; - - return xpath_string_const(qualified_name(na)); - } - - case ast_func_name_1: - { - xpath_allocator_capture cr(stack.result); - - xpath_node_set_raw ns = _left->eval_node_set(c, stack); - xpath_node na = ns.first(); - - return xpath_string_const(qualified_name(na)); - } - - case ast_func_namespace_uri_0: - { - xpath_node na = c.n; - - return xpath_string_const(namespace_uri(na)); - } - - case ast_func_namespace_uri_1: - { - xpath_allocator_capture cr(stack.result); - - xpath_node_set_raw ns = _left->eval_node_set(c, stack); - xpath_node na = ns.first(); - - return xpath_string_const(namespace_uri(na)); - } - - case ast_func_string_0: - return string_value(c.n, stack.result); - - case ast_func_string_1: - return _left->eval_string(c, stack); - - case ast_func_concat: - return eval_string_concat(c, stack); - - case ast_func_substring_before: - { - xpath_allocator_capture cr(stack.temp); - - xpath_stack swapped_stack = {stack.temp, stack.result}; - - xpath_string s = _left->eval_string(c, swapped_stack); - xpath_string p = _right->eval_string(c, swapped_stack); - - const char_t* pos = find_substring(s.c_str(), p.c_str()); - - return pos ? xpath_string(s.c_str(), pos, stack.result) : xpath_string(); - } - - case ast_func_substring_after: - { - xpath_allocator_capture cr(stack.temp); - - xpath_stack swapped_stack = {stack.temp, stack.result}; - - xpath_string s = _left->eval_string(c, swapped_stack); - xpath_string p = _right->eval_string(c, swapped_stack); - - const char_t* pos = find_substring(s.c_str(), p.c_str()); - if (!pos) return xpath_string(); - - const char_t* result = pos + p.length(); - - return s.uses_heap() ? xpath_string(result, stack.result) : xpath_string_const(result); - } - - case ast_func_substring_2: - { - xpath_allocator_capture cr(stack.temp); - - xpath_stack swapped_stack = {stack.temp, stack.result}; - - xpath_string s = _left->eval_string(c, swapped_stack); - size_t s_length = s.length(); - - double first = round_nearest(_right->eval_number(c, stack)); - - if (is_nan(first)) return xpath_string(); // NaN - else if (first >= s_length + 1) return xpath_string(); - - size_t pos = first < 1 ? 1 : static_cast(first); - assert(1 <= pos && pos <= s_length + 1); - - const char_t* rbegin = s.c_str() + (pos - 1); - - return s.uses_heap() ? xpath_string(rbegin, stack.result) : xpath_string_const(rbegin); - } - - case ast_func_substring_3: - { - xpath_allocator_capture cr(stack.temp); - - xpath_stack swapped_stack = {stack.temp, stack.result}; - - xpath_string s = _left->eval_string(c, swapped_stack); - size_t s_length = s.length(); - - double first = round_nearest(_right->eval_number(c, stack)); - double last = first + round_nearest(_right->_next->eval_number(c, stack)); - - if (is_nan(first) || is_nan(last)) return xpath_string(); - else if (first >= s_length + 1) return xpath_string(); - else if (first >= last) return xpath_string(); - else if (last < 1) return xpath_string(); - - size_t pos = first < 1 ? 1 : static_cast(first); - size_t end = last >= s_length + 1 ? s_length + 1 : static_cast(last); - - assert(1 <= pos && pos <= end && end <= s_length + 1); - const char_t* rbegin = s.c_str() + (pos - 1); - const char_t* rend = s.c_str() + (end - 1); - - return (end == s_length + 1 && !s.uses_heap()) ? xpath_string_const(rbegin) : xpath_string(rbegin, rend, stack.result); - } - - case ast_func_normalize_space_0: - { - xpath_string s = string_value(c.n, stack.result); - - normalize_space(s.data(stack.result)); - - return s; - } - - case ast_func_normalize_space_1: - { - xpath_string s = _left->eval_string(c, stack); - - normalize_space(s.data(stack.result)); - - return s; - } - - case ast_func_translate: - { - xpath_allocator_capture cr(stack.temp); - - xpath_stack swapped_stack = {stack.temp, stack.result}; - - xpath_string s = _left->eval_string(c, stack); - xpath_string from = _right->eval_string(c, swapped_stack); - xpath_string to = _right->_next->eval_string(c, swapped_stack); - - translate(s.data(stack.result), from.c_str(), to.c_str()); - - return s; - } - - case ast_variable: - { - assert(_rettype == _data.variable->type()); - - if (_rettype == xpath_type_string) - return xpath_string_const(_data.variable->get_string()); - - // fallthrough to type conversion - } - - default: - { - switch (_rettype) - { - case xpath_type_boolean: - return xpath_string_const(eval_boolean(c, stack) ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false")); - - case xpath_type_number: - return convert_number_to_string(eval_number(c, stack), stack.result); - - case xpath_type_node_set: - { - xpath_allocator_capture cr(stack.temp); - - xpath_stack swapped_stack = {stack.temp, stack.result}; - - xpath_node_set_raw ns = eval_node_set(c, swapped_stack); - return ns.empty() ? xpath_string() : string_value(ns.first(), stack.result); - } - - default: - assert(!"Wrong expression for return type string"); - return xpath_string(); - } - } - } - } - - xpath_node_set_raw eval_node_set(const xpath_context& c, const xpath_stack& stack) - { - switch (_type) - { - case ast_op_union: - { - xpath_allocator_capture cr(stack.temp); - - xpath_stack swapped_stack = {stack.temp, stack.result}; - - xpath_node_set_raw ls = _left->eval_node_set(c, swapped_stack); - xpath_node_set_raw rs = _right->eval_node_set(c, stack); - - // we can optimize merging two sorted sets, but this is a very rare operation, so don't bother - rs.set_type(xpath_node_set::type_unsorted); - - rs.append(ls.begin(), ls.end(), stack.result); - rs.remove_duplicates(); - - return rs; - } - - case ast_filter: - case ast_filter_posinv: - { - xpath_node_set_raw set = _left->eval_node_set(c, stack); - - // either expression is a number or it contains position() call; sort by document order - if (_type == ast_filter) set.sort_do(); - - apply_predicate(set, 0, _right, stack); - - return set; - } - - case ast_func_id: - return xpath_node_set_raw(); - - case ast_step: - { - switch (_axis) - { - case axis_ancestor: - return step_do(c, stack, axis_to_type()); - - case axis_ancestor_or_self: - return step_do(c, stack, axis_to_type()); - - case axis_attribute: - return step_do(c, stack, axis_to_type()); - - case axis_child: - return step_do(c, stack, axis_to_type()); - - case axis_descendant: - return step_do(c, stack, axis_to_type()); - - case axis_descendant_or_self: - return step_do(c, stack, axis_to_type()); - - case axis_following: - return step_do(c, stack, axis_to_type()); - - case axis_following_sibling: - return step_do(c, stack, axis_to_type()); - - case axis_namespace: - // namespaced axis is not supported - return xpath_node_set_raw(); - - case axis_parent: - return step_do(c, stack, axis_to_type()); - - case axis_preceding: - return step_do(c, stack, axis_to_type()); - - case axis_preceding_sibling: - return step_do(c, stack, axis_to_type()); - - case axis_self: - return step_do(c, stack, axis_to_type()); - - default: - assert(!"Unknown axis"); - return xpath_node_set_raw(); - } - } - - case ast_step_root: - { - assert(!_right); // root step can't have any predicates - - xpath_node_set_raw ns; - - ns.set_type(xpath_node_set::type_sorted); - - if (c.n.node()) ns.push_back(c.n.node().root(), stack.result); - else if (c.n.attribute()) ns.push_back(c.n.parent().root(), stack.result); - - return ns; - } - - case ast_variable: - { - assert(_rettype == _data.variable->type()); - - if (_rettype == xpath_type_node_set) - { - const xpath_node_set& s = _data.variable->get_node_set(); - - xpath_node_set_raw ns; - - ns.set_type(s.type()); - ns.append(s.begin(), s.end(), stack.result); - - return ns; - } - - // fallthrough to type conversion - } - - default: - assert(!"Wrong expression for return type node set"); - return xpath_node_set_raw(); - } - } - - bool is_posinv() - { - switch (_type) - { - case ast_func_position: - return false; - - case ast_string_constant: - case ast_number_constant: - case ast_variable: - return true; - - case ast_step: - case ast_step_root: - return true; - - case ast_predicate: - case ast_filter: - case ast_filter_posinv: - return true; - - default: - if (_left && !_left->is_posinv()) return false; - - for (xpath_ast_node* n = _right; n; n = n->_next) - if (!n->is_posinv()) return false; - - return true; - } - } - - xpath_value_type rettype() const - { - return static_cast(_rettype); - } - }; - - struct xpath_parser - { - xpath_allocator* _alloc; - xpath_lexer _lexer; - - const char_t* _query; - xpath_variable_set* _variables; - - xpath_parse_result* _result; - - #ifdef PUGIXML_NO_EXCEPTIONS - jmp_buf _error_handler; - #endif - - void throw_error(const char* message) - { - _result->error = message; - _result->offset = _lexer.current_pos() - _query; - - #ifdef PUGIXML_NO_EXCEPTIONS - longjmp(_error_handler, 1); - #else - throw xpath_exception(*_result); - #endif - } - - void throw_error_oom() - { - #ifdef PUGIXML_NO_EXCEPTIONS - throw_error("Out of memory"); - #else - throw std::bad_alloc(); - #endif - } - - void* alloc_node() - { - void* result = _alloc->allocate_nothrow(sizeof(xpath_ast_node)); - - if (!result) throw_error_oom(); - - return result; - } - - const char_t* alloc_string(const xpath_lexer_string& value) - { - if (value.begin) - { - size_t length = static_cast(value.end - value.begin); - - char_t* c = static_cast(_alloc->allocate_nothrow((length + 1) * sizeof(char_t))); - if (!c) throw_error_oom(); - - memcpy(c, value.begin, length * sizeof(char_t)); - c[length] = 0; - - return c; - } - else return 0; - } - - xpath_ast_node* parse_function_helper(ast_type_t type0, ast_type_t type1, size_t argc, xpath_ast_node* args[2]) - { - assert(argc <= 1); - - if (argc == 1 && args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); - - return new (alloc_node()) xpath_ast_node(argc == 0 ? type0 : type1, xpath_type_string, args[0]); - } - - xpath_ast_node* parse_function(const xpath_lexer_string& name, size_t argc, xpath_ast_node* args[2]) - { - switch (name.begin[0]) - { - case 'b': - if (name == PUGIXML_TEXT("boolean") && argc == 1) - return new (alloc_node()) xpath_ast_node(ast_func_boolean, xpath_type_boolean, args[0]); - - break; - - case 'c': - if (name == PUGIXML_TEXT("count") && argc == 1) - { - if (args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); - return new (alloc_node()) xpath_ast_node(ast_func_count, xpath_type_number, args[0]); - } - else if (name == PUGIXML_TEXT("contains") && argc == 2) - return new (alloc_node()) xpath_ast_node(ast_func_contains, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("concat") && argc >= 2) - return new (alloc_node()) xpath_ast_node(ast_func_concat, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("ceiling") && argc == 1) - return new (alloc_node()) xpath_ast_node(ast_func_ceiling, xpath_type_number, args[0]); - - break; - - case 'f': - if (name == PUGIXML_TEXT("false") && argc == 0) - return new (alloc_node()) xpath_ast_node(ast_func_false, xpath_type_boolean); - else if (name == PUGIXML_TEXT("floor") && argc == 1) - return new (alloc_node()) xpath_ast_node(ast_func_floor, xpath_type_number, args[0]); - - break; - - case 'i': - if (name == PUGIXML_TEXT("id") && argc == 1) - return new (alloc_node()) xpath_ast_node(ast_func_id, xpath_type_node_set, args[0]); - - break; - - case 'l': - if (name == PUGIXML_TEXT("last") && argc == 0) - return new (alloc_node()) xpath_ast_node(ast_func_last, xpath_type_number); - else if (name == PUGIXML_TEXT("lang") && argc == 1) - return new (alloc_node()) xpath_ast_node(ast_func_lang, xpath_type_boolean, args[0]); - else if (name == PUGIXML_TEXT("local-name") && argc <= 1) - return parse_function_helper(ast_func_local_name_0, ast_func_local_name_1, argc, args); - - break; - - case 'n': - if (name == PUGIXML_TEXT("name") && argc <= 1) - return parse_function_helper(ast_func_name_0, ast_func_name_1, argc, args); - else if (name == PUGIXML_TEXT("namespace-uri") && argc <= 1) - return parse_function_helper(ast_func_namespace_uri_0, ast_func_namespace_uri_1, argc, args); - else if (name == PUGIXML_TEXT("normalize-space") && argc <= 1) - return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_normalize_space_0 : ast_func_normalize_space_1, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("not") && argc == 1) - return new (alloc_node()) xpath_ast_node(ast_func_not, xpath_type_boolean, args[0]); - else if (name == PUGIXML_TEXT("number") && argc <= 1) - return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_number_0 : ast_func_number_1, xpath_type_number, args[0]); - - break; - - case 'p': - if (name == PUGIXML_TEXT("position") && argc == 0) - return new (alloc_node()) xpath_ast_node(ast_func_position, xpath_type_number); - - break; - - case 'r': - if (name == PUGIXML_TEXT("round") && argc == 1) - return new (alloc_node()) xpath_ast_node(ast_func_round, xpath_type_number, args[0]); - - break; - - case 's': - if (name == PUGIXML_TEXT("string") && argc <= 1) - return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_string_0 : ast_func_string_1, xpath_type_string, args[0]); - else if (name == PUGIXML_TEXT("string-length") && argc <= 1) - return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_string_length_0 : ast_func_string_length_1, xpath_type_string, args[0]); - else if (name == PUGIXML_TEXT("starts-with") && argc == 2) - return new (alloc_node()) xpath_ast_node(ast_func_starts_with, xpath_type_boolean, args[0], args[1]); - else if (name == PUGIXML_TEXT("substring-before") && argc == 2) - return new (alloc_node()) xpath_ast_node(ast_func_substring_before, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("substring-after") && argc == 2) - return new (alloc_node()) xpath_ast_node(ast_func_substring_after, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("substring") && (argc == 2 || argc == 3)) - return new (alloc_node()) xpath_ast_node(argc == 2 ? ast_func_substring_2 : ast_func_substring_3, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("sum") && argc == 1) - { - if (args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); - return new (alloc_node()) xpath_ast_node(ast_func_sum, xpath_type_number, args[0]); - } - - break; - - case 't': - if (name == PUGIXML_TEXT("translate") && argc == 3) - return new (alloc_node()) xpath_ast_node(ast_func_translate, xpath_type_string, args[0], args[1]); - else if (name == PUGIXML_TEXT("true") && argc == 0) - return new (alloc_node()) xpath_ast_node(ast_func_true, xpath_type_boolean); - - break; - - default: - break; - } - - throw_error("Unrecognized function or wrong parameter count"); - - return 0; - } - - axis_t parse_axis_name(const xpath_lexer_string& name, bool& specified) - { - specified = true; - - switch (name.begin[0]) - { - case 'a': - if (name == PUGIXML_TEXT("ancestor")) - return axis_ancestor; - else if (name == PUGIXML_TEXT("ancestor-or-self")) - return axis_ancestor_or_self; - else if (name == PUGIXML_TEXT("attribute")) - return axis_attribute; - - break; - - case 'c': - if (name == PUGIXML_TEXT("child")) - return axis_child; - - break; - - case 'd': - if (name == PUGIXML_TEXT("descendant")) - return axis_descendant; - else if (name == PUGIXML_TEXT("descendant-or-self")) - return axis_descendant_or_self; - - break; - - case 'f': - if (name == PUGIXML_TEXT("following")) - return axis_following; - else if (name == PUGIXML_TEXT("following-sibling")) - return axis_following_sibling; - - break; - - case 'n': - if (name == PUGIXML_TEXT("namespace")) - return axis_namespace; - - break; - - case 'p': - if (name == PUGIXML_TEXT("parent")) - return axis_parent; - else if (name == PUGIXML_TEXT("preceding")) - return axis_preceding; - else if (name == PUGIXML_TEXT("preceding-sibling")) - return axis_preceding_sibling; - - break; - - case 's': - if (name == PUGIXML_TEXT("self")) - return axis_self; - - break; - - default: - break; - } - - specified = false; - return axis_child; - } - - nodetest_t parse_node_test_type(const xpath_lexer_string& name) - { - switch (name.begin[0]) - { - case 'c': - if (name == PUGIXML_TEXT("comment")) - return nodetest_type_comment; - - break; - - case 'n': - if (name == PUGIXML_TEXT("node")) - return nodetest_type_node; - - break; - - case 'p': - if (name == PUGIXML_TEXT("processing-instruction")) - return nodetest_type_pi; - - break; - - case 't': - if (name == PUGIXML_TEXT("text")) - return nodetest_type_text; - - break; - - default: - break; - } - - return nodetest_none; - } - - // PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall - xpath_ast_node* parse_primary_expression() - { - switch (_lexer.current()) - { - case lex_var_ref: - { - xpath_lexer_string name = _lexer.contents(); - - if (!_variables) - throw_error("Unknown variable: variable set is not provided"); - - xpath_variable* var = get_variable(_variables, name.begin, name.end); - - if (!var) - throw_error("Unknown variable: variable set does not contain the given name"); - - _lexer.next(); - - return new (alloc_node()) xpath_ast_node(ast_variable, var->type(), var); - } - - case lex_open_brace: - { - _lexer.next(); - - xpath_ast_node* n = parse_expression(); - - if (_lexer.current() != lex_close_brace) - throw_error("Unmatched braces"); - - _lexer.next(); - - return n; - } - - case lex_quoted_string: - { - const char_t* value = alloc_string(_lexer.contents()); - - xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_string_constant, xpath_type_string, value); - _lexer.next(); - - return n; - } - - case lex_number: - { - double value = 0; - - if (!convert_string_to_number(_lexer.contents().begin, _lexer.contents().end, &value)) - throw_error_oom(); - - xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_number_constant, xpath_type_number, value); - _lexer.next(); - - return n; - } - - case lex_string: - { - xpath_ast_node* args[2] = {0}; - size_t argc = 0; - - xpath_lexer_string function = _lexer.contents(); - _lexer.next(); - - xpath_ast_node* last_arg = 0; - - if (_lexer.current() != lex_open_brace) - throw_error("Unrecognized function call"); - _lexer.next(); - - if (_lexer.current() != lex_close_brace) - args[argc++] = parse_expression(); - - while (_lexer.current() != lex_close_brace) - { - if (_lexer.current() != lex_comma) - throw_error("No comma between function arguments"); - _lexer.next(); - - xpath_ast_node* n = parse_expression(); - - if (argc < 2) args[argc] = n; - else last_arg->set_next(n); - - argc++; - last_arg = n; - } - - _lexer.next(); - - return parse_function(function, argc, args); - } - - default: - throw_error("Unrecognizable primary expression"); - - return 0; - } - } - - // FilterExpr ::= PrimaryExpr | FilterExpr Predicate - // Predicate ::= '[' PredicateExpr ']' - // PredicateExpr ::= Expr - xpath_ast_node* parse_filter_expression() - { - xpath_ast_node* n = parse_primary_expression(); - - while (_lexer.current() == lex_open_square_brace) - { - _lexer.next(); - - xpath_ast_node* expr = parse_expression(); - - if (n->rettype() != xpath_type_node_set) throw_error("Predicate has to be applied to node set"); - - bool posinv = expr->rettype() != xpath_type_number && expr->is_posinv(); - - n = new (alloc_node()) xpath_ast_node(posinv ? ast_filter_posinv : ast_filter, xpath_type_node_set, n, expr); - - if (_lexer.current() != lex_close_square_brace) - throw_error("Unmatched square brace"); - - _lexer.next(); - } - - return n; - } - - // Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep - // AxisSpecifier ::= AxisName '::' | '@'? - // NodeTest ::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')' - // NameTest ::= '*' | NCName ':' '*' | QName - // AbbreviatedStep ::= '.' | '..' - xpath_ast_node* parse_step(xpath_ast_node* set) - { - if (set && set->rettype() != xpath_type_node_set) - throw_error("Step has to be applied to node set"); - - bool axis_specified = false; - axis_t axis = axis_child; // implied child axis - - if (_lexer.current() == lex_axis_attribute) - { - axis = axis_attribute; - axis_specified = true; - - _lexer.next(); - } - else if (_lexer.current() == lex_dot) - { - _lexer.next(); - - return new (alloc_node()) xpath_ast_node(ast_step, set, axis_self, nodetest_type_node, 0); - } - else if (_lexer.current() == lex_double_dot) - { - _lexer.next(); - - return new (alloc_node()) xpath_ast_node(ast_step, set, axis_parent, nodetest_type_node, 0); - } - - nodetest_t nt_type = nodetest_none; - xpath_lexer_string nt_name; - - if (_lexer.current() == lex_string) - { - // node name test - nt_name = _lexer.contents(); - _lexer.next(); - - // was it an axis name? - if (_lexer.current() == lex_double_colon) - { - // parse axis name - if (axis_specified) throw_error("Two axis specifiers in one step"); - - axis = parse_axis_name(nt_name, axis_specified); - - if (!axis_specified) throw_error("Unknown axis"); - - // read actual node test - _lexer.next(); - - if (_lexer.current() == lex_multiply) - { - nt_type = nodetest_all; - nt_name = xpath_lexer_string(); - _lexer.next(); - } - else if (_lexer.current() == lex_string) - { - nt_name = _lexer.contents(); - _lexer.next(); - } - else throw_error("Unrecognized node test"); - } - - if (nt_type == nodetest_none) - { - // node type test or processing-instruction - if (_lexer.current() == lex_open_brace) - { - _lexer.next(); - - if (_lexer.current() == lex_close_brace) - { - _lexer.next(); - - nt_type = parse_node_test_type(nt_name); - - if (nt_type == nodetest_none) throw_error("Unrecognized node type"); - - nt_name = xpath_lexer_string(); - } - else if (nt_name == PUGIXML_TEXT("processing-instruction")) - { - if (_lexer.current() != lex_quoted_string) - throw_error("Only literals are allowed as arguments to processing-instruction()"); - - nt_type = nodetest_pi; - nt_name = _lexer.contents(); - _lexer.next(); - - if (_lexer.current() != lex_close_brace) - throw_error("Unmatched brace near processing-instruction()"); - _lexer.next(); - } - else - throw_error("Unmatched brace near node type test"); - - } - // QName or NCName:* - else - { - if (nt_name.end - nt_name.begin > 2 && nt_name.end[-2] == ':' && nt_name.end[-1] == '*') // NCName:* - { - nt_name.end--; // erase * - - nt_type = nodetest_all_in_namespace; - } - else nt_type = nodetest_name; - } - } - } - else if (_lexer.current() == lex_multiply) - { - nt_type = nodetest_all; - _lexer.next(); - } - else throw_error("Unrecognized node test"); - - xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step, set, axis, nt_type, alloc_string(nt_name)); - - xpath_ast_node* last = 0; - - while (_lexer.current() == lex_open_square_brace) - { - _lexer.next(); - - xpath_ast_node* expr = parse_expression(); - - xpath_ast_node* pred = new (alloc_node()) xpath_ast_node(ast_predicate, xpath_type_node_set, expr); - - if (_lexer.current() != lex_close_square_brace) - throw_error("Unmatched square brace"); - _lexer.next(); - - if (last) last->set_next(pred); - else n->set_right(pred); - - last = pred; - } - - return n; - } - - // RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | RelativeLocationPath '//' Step - xpath_ast_node* parse_relative_location_path(xpath_ast_node* set) - { - xpath_ast_node* n = parse_step(set); - - while (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) - { - lexeme_t l = _lexer.current(); - _lexer.next(); - - if (l == lex_double_slash) - n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); - - n = parse_step(n); - } - - return n; - } - - // LocationPath ::= RelativeLocationPath | AbsoluteLocationPath - // AbsoluteLocationPath ::= '/' RelativeLocationPath? | '//' RelativeLocationPath - xpath_ast_node* parse_location_path() - { - if (_lexer.current() == lex_slash) - { - _lexer.next(); - - xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set); - - // relative location path can start from axis_attribute, dot, double_dot, multiply and string lexemes; any other lexeme means standalone root path - lexeme_t l = _lexer.current(); - - if (l == lex_string || l == lex_axis_attribute || l == lex_dot || l == lex_double_dot || l == lex_multiply) - return parse_relative_location_path(n); - else - return n; - } - else if (_lexer.current() == lex_double_slash) - { - _lexer.next(); - - xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set); - n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); - - return parse_relative_location_path(n); - } - - // else clause moved outside of if because of bogus warning 'control may reach end of non-void function being inlined' in gcc 4.0.1 - return parse_relative_location_path(0); - } - - // PathExpr ::= LocationPath - // | FilterExpr - // | FilterExpr '/' RelativeLocationPath - // | FilterExpr '//' RelativeLocationPath - xpath_ast_node* parse_path_expression() - { - // Clarification. - // PathExpr begins with either LocationPath or FilterExpr. - // FilterExpr begins with PrimaryExpr - // PrimaryExpr begins with '$' in case of it being a variable reference, - // '(' in case of it being an expression, string literal, number constant or - // function call. - - if (_lexer.current() == lex_var_ref || _lexer.current() == lex_open_brace || - _lexer.current() == lex_quoted_string || _lexer.current() == lex_number || - _lexer.current() == lex_string) - { - if (_lexer.current() == lex_string) - { - // This is either a function call, or not - if not, we shall proceed with location path - const char_t* state = _lexer.state(); - - while (PUGI__IS_CHARTYPE(*state, ct_space)) ++state; - - if (*state != '(') return parse_location_path(); - - // This looks like a function call; however this still can be a node-test. Check it. - if (parse_node_test_type(_lexer.contents()) != nodetest_none) return parse_location_path(); - } - - xpath_ast_node* n = parse_filter_expression(); - - if (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) - { - lexeme_t l = _lexer.current(); - _lexer.next(); - - if (l == lex_double_slash) - { - if (n->rettype() != xpath_type_node_set) throw_error("Step has to be applied to node set"); - - n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); - } - - // select from location path - return parse_relative_location_path(n); - } - - return n; - } - else return parse_location_path(); - } - - // UnionExpr ::= PathExpr | UnionExpr '|' PathExpr - xpath_ast_node* parse_union_expression() - { - xpath_ast_node* n = parse_path_expression(); - - while (_lexer.current() == lex_union) - { - _lexer.next(); - - xpath_ast_node* expr = parse_union_expression(); - - if (n->rettype() != xpath_type_node_set || expr->rettype() != xpath_type_node_set) - throw_error("Union operator has to be applied to node sets"); - - n = new (alloc_node()) xpath_ast_node(ast_op_union, xpath_type_node_set, n, expr); - } - - return n; - } - - // UnaryExpr ::= UnionExpr | '-' UnaryExpr - xpath_ast_node* parse_unary_expression() - { - if (_lexer.current() == lex_minus) - { - _lexer.next(); - - xpath_ast_node* expr = parse_unary_expression(); - - return new (alloc_node()) xpath_ast_node(ast_op_negate, xpath_type_number, expr); - } - else return parse_union_expression(); - } - - // MultiplicativeExpr ::= UnaryExpr - // | MultiplicativeExpr '*' UnaryExpr - // | MultiplicativeExpr 'div' UnaryExpr - // | MultiplicativeExpr 'mod' UnaryExpr - xpath_ast_node* parse_multiplicative_expression() - { - xpath_ast_node* n = parse_unary_expression(); - - while (_lexer.current() == lex_multiply || (_lexer.current() == lex_string && - (_lexer.contents() == PUGIXML_TEXT("mod") || _lexer.contents() == PUGIXML_TEXT("div")))) - { - ast_type_t op = _lexer.current() == lex_multiply ? ast_op_multiply : - _lexer.contents().begin[0] == 'd' ? ast_op_divide : ast_op_mod; - _lexer.next(); - - xpath_ast_node* expr = parse_unary_expression(); - - n = new (alloc_node()) xpath_ast_node(op, xpath_type_number, n, expr); - } - - return n; - } - - // AdditiveExpr ::= MultiplicativeExpr - // | AdditiveExpr '+' MultiplicativeExpr - // | AdditiveExpr '-' MultiplicativeExpr - xpath_ast_node* parse_additive_expression() - { - xpath_ast_node* n = parse_multiplicative_expression(); - - while (_lexer.current() == lex_plus || _lexer.current() == lex_minus) - { - lexeme_t l = _lexer.current(); - - _lexer.next(); - - xpath_ast_node* expr = parse_multiplicative_expression(); - - n = new (alloc_node()) xpath_ast_node(l == lex_plus ? ast_op_add : ast_op_subtract, xpath_type_number, n, expr); - } - - return n; - } - - // RelationalExpr ::= AdditiveExpr - // | RelationalExpr '<' AdditiveExpr - // | RelationalExpr '>' AdditiveExpr - // | RelationalExpr '<=' AdditiveExpr - // | RelationalExpr '>=' AdditiveExpr - xpath_ast_node* parse_relational_expression() - { - xpath_ast_node* n = parse_additive_expression(); - - while (_lexer.current() == lex_less || _lexer.current() == lex_less_or_equal || - _lexer.current() == lex_greater || _lexer.current() == lex_greater_or_equal) - { - lexeme_t l = _lexer.current(); - _lexer.next(); - - xpath_ast_node* expr = parse_additive_expression(); - - n = new (alloc_node()) xpath_ast_node(l == lex_less ? ast_op_less : l == lex_greater ? ast_op_greater : - l == lex_less_or_equal ? ast_op_less_or_equal : ast_op_greater_or_equal, xpath_type_boolean, n, expr); - } - - return n; - } - - // EqualityExpr ::= RelationalExpr - // | EqualityExpr '=' RelationalExpr - // | EqualityExpr '!=' RelationalExpr - xpath_ast_node* parse_equality_expression() - { - xpath_ast_node* n = parse_relational_expression(); - - while (_lexer.current() == lex_equal || _lexer.current() == lex_not_equal) - { - lexeme_t l = _lexer.current(); - - _lexer.next(); - - xpath_ast_node* expr = parse_relational_expression(); - - n = new (alloc_node()) xpath_ast_node(l == lex_equal ? ast_op_equal : ast_op_not_equal, xpath_type_boolean, n, expr); - } - - return n; - } - - // AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr - xpath_ast_node* parse_and_expression() - { - xpath_ast_node* n = parse_equality_expression(); - - while (_lexer.current() == lex_string && _lexer.contents() == PUGIXML_TEXT("and")) - { - _lexer.next(); - - xpath_ast_node* expr = parse_equality_expression(); - - n = new (alloc_node()) xpath_ast_node(ast_op_and, xpath_type_boolean, n, expr); - } - - return n; - } - - // OrExpr ::= AndExpr | OrExpr 'or' AndExpr - xpath_ast_node* parse_or_expression() - { - xpath_ast_node* n = parse_and_expression(); - - while (_lexer.current() == lex_string && _lexer.contents() == PUGIXML_TEXT("or")) - { - _lexer.next(); - - xpath_ast_node* expr = parse_and_expression(); - - n = new (alloc_node()) xpath_ast_node(ast_op_or, xpath_type_boolean, n, expr); - } - - return n; - } - - // Expr ::= OrExpr - xpath_ast_node* parse_expression() - { - return parse_or_expression(); - } - - xpath_parser(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result): _alloc(alloc), _lexer(query), _query(query), _variables(variables), _result(result) - { - } - - xpath_ast_node* parse() - { - xpath_ast_node* result = parse_expression(); - - if (_lexer.current() != lex_eof) - { - // there are still unparsed tokens left, error - throw_error("Incorrect query"); - } - - return result; - } - - static xpath_ast_node* parse(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result) - { - xpath_parser parser(query, variables, alloc, result); - - #ifdef PUGIXML_NO_EXCEPTIONS - int error = setjmp(parser._error_handler); - - return (error == 0) ? parser.parse() : 0; - #else - return parser.parse(); - #endif - } - }; - - struct xpath_query_impl - { - static xpath_query_impl* create() - { - void* memory = xml_memory::allocate(sizeof(xpath_query_impl)); - - return new (memory) xpath_query_impl(); - } - - static void destroy(void* ptr) - { - if (!ptr) return; - - // free all allocated pages - static_cast(ptr)->alloc.release(); - - // free allocator memory (with the first page) - xml_memory::deallocate(ptr); - } - - xpath_query_impl(): root(0), alloc(&block) - { - block.next = 0; - } - - xpath_ast_node* root; - xpath_allocator alloc; - xpath_memory_block block; - }; - - PUGI__FN xpath_string evaluate_string_impl(xpath_query_impl* impl, const xpath_node& n, xpath_stack_data& sd) - { - if (!impl) return xpath_string(); - - #ifdef PUGIXML_NO_EXCEPTIONS - if (setjmp(sd.error_handler)) return xpath_string(); - #endif - - xpath_context c(n, 1, 1); - - return impl->root->eval_string(c, sd.stack); - } -PUGI__NS_END - -namespace pugi -{ -#ifndef PUGIXML_NO_EXCEPTIONS - PUGI__FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_) - { - assert(_result.error); - } - - PUGI__FN const char* xpath_exception::what() const throw() - { - return _result.error; - } - - PUGI__FN const xpath_parse_result& xpath_exception::result() const - { - return _result; - } -#endif - - PUGI__FN xpath_node::xpath_node() - { - } - - PUGI__FN xpath_node::xpath_node(const xml_node& node_): _node(node_) - { - } - - PUGI__FN xpath_node::xpath_node(const xml_attribute& attribute_, const xml_node& parent_): _node(attribute_ ? parent_ : xml_node()), _attribute(attribute_) - { - } - - PUGI__FN xml_node xpath_node::node() const - { - return _attribute ? xml_node() : _node; - } - - PUGI__FN xml_attribute xpath_node::attribute() const - { - return _attribute; - } - - PUGI__FN xml_node xpath_node::parent() const - { - return _attribute ? _node : _node.parent(); - } - - PUGI__FN static void unspecified_bool_xpath_node(xpath_node***) - { - } - - PUGI__FN xpath_node::operator xpath_node::unspecified_bool_type() const - { - return (_node || _attribute) ? unspecified_bool_xpath_node : 0; - } - - PUGI__FN bool xpath_node::operator!() const - { - return !(_node || _attribute); - } - - PUGI__FN bool xpath_node::operator==(const xpath_node& n) const - { - return _node == n._node && _attribute == n._attribute; - } - - PUGI__FN bool xpath_node::operator!=(const xpath_node& n) const - { - return _node != n._node || _attribute != n._attribute; - } - -#ifdef __BORLANDC__ - PUGI__FN bool operator&&(const xpath_node& lhs, bool rhs) - { - return (bool)lhs && rhs; - } - - PUGI__FN bool operator||(const xpath_node& lhs, bool rhs) - { - return (bool)lhs || rhs; - } -#endif - - PUGI__FN void xpath_node_set::_assign(const_iterator begin_, const_iterator end_) - { - assert(begin_ <= end_); - - size_t size_ = static_cast(end_ - begin_); - - if (size_ <= 1) - { - // deallocate old buffer - if (_begin != &_storage) impl::xml_memory::deallocate(_begin); - - // use internal buffer - if (begin_ != end_) _storage = *begin_; - - _begin = &_storage; - _end = &_storage + size_; - } - else - { - // make heap copy - xpath_node* storage = static_cast(impl::xml_memory::allocate(size_ * sizeof(xpath_node))); - - if (!storage) - { - #ifdef PUGIXML_NO_EXCEPTIONS - return; - #else - throw std::bad_alloc(); - #endif - } - - memcpy(storage, begin_, size_ * sizeof(xpath_node)); - - // deallocate old buffer - if (_begin != &_storage) impl::xml_memory::deallocate(_begin); - - // finalize - _begin = storage; - _end = storage + size_; - } - } - - PUGI__FN xpath_node_set::xpath_node_set(): _type(type_unsorted), _begin(&_storage), _end(&_storage) - { - } - - PUGI__FN xpath_node_set::xpath_node_set(const_iterator begin_, const_iterator end_, type_t type_): _type(type_), _begin(&_storage), _end(&_storage) - { - _assign(begin_, end_); - } - - PUGI__FN xpath_node_set::~xpath_node_set() - { - if (_begin != &_storage) impl::xml_memory::deallocate(_begin); - } - - PUGI__FN xpath_node_set::xpath_node_set(const xpath_node_set& ns): _type(ns._type), _begin(&_storage), _end(&_storage) - { - _assign(ns._begin, ns._end); - } - - PUGI__FN xpath_node_set& xpath_node_set::operator=(const xpath_node_set& ns) - { - if (this == &ns) return *this; - - _type = ns._type; - _assign(ns._begin, ns._end); - - return *this; - } - - PUGI__FN xpath_node_set::type_t xpath_node_set::type() const - { - return _type; - } - - PUGI__FN size_t xpath_node_set::size() const - { - return _end - _begin; - } - - PUGI__FN bool xpath_node_set::empty() const - { - return _begin == _end; - } - - PUGI__FN const xpath_node& xpath_node_set::operator[](size_t index) const - { - assert(index < size()); - return _begin[index]; - } - - PUGI__FN xpath_node_set::const_iterator xpath_node_set::begin() const - { - return _begin; - } - - PUGI__FN xpath_node_set::const_iterator xpath_node_set::end() const - { - return _end; - } - - PUGI__FN void xpath_node_set::sort(bool reverse) - { - _type = impl::xpath_sort(_begin, _end, _type, reverse); - } - - PUGI__FN xpath_node xpath_node_set::first() const - { - return impl::xpath_first(_begin, _end, _type); - } - - PUGI__FN xpath_parse_result::xpath_parse_result(): error("Internal error"), offset(0) - { - } - - PUGI__FN xpath_parse_result::operator bool() const - { - return error == 0; - } - - PUGI__FN const char* xpath_parse_result::description() const - { - return error ? error : "No error"; - } - - PUGI__FN xpath_variable::xpath_variable() - { - } - - PUGI__FN const char_t* xpath_variable::name() const - { - switch (_type) - { - case xpath_type_node_set: - return static_cast(this)->name; - - case xpath_type_number: - return static_cast(this)->name; - - case xpath_type_string: - return static_cast(this)->name; - - case xpath_type_boolean: - return static_cast(this)->name; - - default: - assert(!"Invalid variable type"); - return 0; - } - } - - PUGI__FN xpath_value_type xpath_variable::type() const - { - return _type; - } - - PUGI__FN bool xpath_variable::get_boolean() const - { - return (_type == xpath_type_boolean) ? static_cast(this)->value : false; - } - - PUGI__FN double xpath_variable::get_number() const - { - return (_type == xpath_type_number) ? static_cast(this)->value : impl::gen_nan(); - } - - PUGI__FN const char_t* xpath_variable::get_string() const - { - const char_t* value = (_type == xpath_type_string) ? static_cast(this)->value : 0; - return value ? value : PUGIXML_TEXT(""); - } - - PUGI__FN const xpath_node_set& xpath_variable::get_node_set() const - { - return (_type == xpath_type_node_set) ? static_cast(this)->value : impl::dummy_node_set; - } - - PUGI__FN bool xpath_variable::set(bool value) - { - if (_type != xpath_type_boolean) return false; - - static_cast(this)->value = value; - return true; - } - - PUGI__FN bool xpath_variable::set(double value) - { - if (_type != xpath_type_number) return false; - - static_cast(this)->value = value; - return true; - } - - PUGI__FN bool xpath_variable::set(const char_t* value) - { - if (_type != xpath_type_string) return false; - - impl::xpath_variable_string* var = static_cast(this); - - // duplicate string - size_t size = (impl::strlength(value) + 1) * sizeof(char_t); - - char_t* copy = static_cast(impl::xml_memory::allocate(size)); - if (!copy) return false; - - memcpy(copy, value, size); - - // replace old string - if (var->value) impl::xml_memory::deallocate(var->value); - var->value = copy; - - return true; - } - - PUGI__FN bool xpath_variable::set(const xpath_node_set& value) - { - if (_type != xpath_type_node_set) return false; - - static_cast(this)->value = value; - return true; - } - - PUGI__FN xpath_variable_set::xpath_variable_set() - { - for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) _data[i] = 0; - } - - PUGI__FN xpath_variable_set::~xpath_variable_set() - { - for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) - { - xpath_variable* var = _data[i]; - - while (var) - { - xpath_variable* next = var->_next; - - impl::delete_xpath_variable(var->_type, var); - - var = next; - } - } - } - - PUGI__FN xpath_variable* xpath_variable_set::find(const char_t* name) const - { - const size_t hash_size = sizeof(_data) / sizeof(_data[0]); - size_t hash = impl::hash_string(name) % hash_size; - - // look for existing variable - for (xpath_variable* var = _data[hash]; var; var = var->_next) - if (impl::strequal(var->name(), name)) - return var; - - return 0; - } - - PUGI__FN xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type) - { - const size_t hash_size = sizeof(_data) / sizeof(_data[0]); - size_t hash = impl::hash_string(name) % hash_size; - - // look for existing variable - for (xpath_variable* var = _data[hash]; var; var = var->_next) - if (impl::strequal(var->name(), name)) - return var->type() == type ? var : 0; - - // add new variable - xpath_variable* result = impl::new_xpath_variable(type, name); - - if (result) - { - result->_type = type; - result->_next = _data[hash]; - - _data[hash] = result; - } - - return result; - } - - PUGI__FN bool xpath_variable_set::set(const char_t* name, bool value) - { - xpath_variable* var = add(name, xpath_type_boolean); - return var ? var->set(value) : false; - } - - PUGI__FN bool xpath_variable_set::set(const char_t* name, double value) - { - xpath_variable* var = add(name, xpath_type_number); - return var ? var->set(value) : false; - } - - PUGI__FN bool xpath_variable_set::set(const char_t* name, const char_t* value) - { - xpath_variable* var = add(name, xpath_type_string); - return var ? var->set(value) : false; - } - - PUGI__FN bool xpath_variable_set::set(const char_t* name, const xpath_node_set& value) - { - xpath_variable* var = add(name, xpath_type_node_set); - return var ? var->set(value) : false; - } - - PUGI__FN xpath_variable* xpath_variable_set::get(const char_t* name) - { - return find(name); - } - - PUGI__FN const xpath_variable* xpath_variable_set::get(const char_t* name) const - { - return find(name); - } - - PUGI__FN xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables): _impl(0) - { - impl::xpath_query_impl* qimpl = impl::xpath_query_impl::create(); - - if (!qimpl) - { - #ifdef PUGIXML_NO_EXCEPTIONS - _result.error = "Out of memory"; - #else - throw std::bad_alloc(); - #endif - } - else - { - impl::buffer_holder impl_holder(qimpl, impl::xpath_query_impl::destroy); - - qimpl->root = impl::xpath_parser::parse(query, variables, &qimpl->alloc, &_result); - - if (qimpl->root) - { - _impl = static_cast(impl_holder.release()); - _result.error = 0; - } - } - } - - PUGI__FN xpath_query::~xpath_query() - { - impl::xpath_query_impl::destroy(_impl); - } - - PUGI__FN xpath_value_type xpath_query::return_type() const - { - if (!_impl) return xpath_type_none; - - return static_cast(_impl)->root->rettype(); - } - - PUGI__FN bool xpath_query::evaluate_boolean(const xpath_node& n) const - { - if (!_impl) return false; - - impl::xpath_context c(n, 1, 1); - impl::xpath_stack_data sd; - - #ifdef PUGIXML_NO_EXCEPTIONS - if (setjmp(sd.error_handler)) return false; - #endif - - return static_cast(_impl)->root->eval_boolean(c, sd.stack); - } - - PUGI__FN double xpath_query::evaluate_number(const xpath_node& n) const - { - if (!_impl) return impl::gen_nan(); - - impl::xpath_context c(n, 1, 1); - impl::xpath_stack_data sd; - - #ifdef PUGIXML_NO_EXCEPTIONS - if (setjmp(sd.error_handler)) return impl::gen_nan(); - #endif - - return static_cast(_impl)->root->eval_number(c, sd.stack); - } - -#ifndef PUGIXML_NO_STL - PUGI__FN string_t xpath_query::evaluate_string(const xpath_node& n) const - { - impl::xpath_stack_data sd; - - return impl::evaluate_string_impl(static_cast(_impl), n, sd).c_str(); - } -#endif - - PUGI__FN size_t xpath_query::evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const - { - impl::xpath_stack_data sd; - - impl::xpath_string r = impl::evaluate_string_impl(static_cast(_impl), n, sd); - - size_t full_size = r.length() + 1; - - if (capacity > 0) - { - size_t size = (full_size < capacity) ? full_size : capacity; - assert(size > 0); - - memcpy(buffer, r.c_str(), (size - 1) * sizeof(char_t)); - buffer[size - 1] = 0; - } - - return full_size; - } - - PUGI__FN xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const - { - if (!_impl) return xpath_node_set(); - - impl::xpath_ast_node* root = static_cast(_impl)->root; - - if (root->rettype() != xpath_type_node_set) - { - #ifdef PUGIXML_NO_EXCEPTIONS - return xpath_node_set(); - #else - xpath_parse_result res; - res.error = "Expression does not evaluate to node set"; - - throw xpath_exception(res); - #endif - } - - impl::xpath_context c(n, 1, 1); - impl::xpath_stack_data sd; - - #ifdef PUGIXML_NO_EXCEPTIONS - if (setjmp(sd.error_handler)) return xpath_node_set(); - #endif - - impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack); - - return xpath_node_set(r.begin(), r.end(), r.type()); - } - - PUGI__FN const xpath_parse_result& xpath_query::result() const - { - return _result; - } - - PUGI__FN static void unspecified_bool_xpath_query(xpath_query***) - { - } - - PUGI__FN xpath_query::operator xpath_query::unspecified_bool_type() const - { - return _impl ? unspecified_bool_xpath_query : 0; - } - - PUGI__FN bool xpath_query::operator!() const - { - return !_impl; - } - - PUGI__FN xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables) const - { - xpath_query q(query, variables); - return select_single_node(q); - } - - PUGI__FN xpath_node xml_node::select_single_node(const xpath_query& query) const - { - xpath_node_set s = query.evaluate_node_set(*this); - return s.empty() ? xpath_node() : s.first(); - } - - PUGI__FN xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables) const - { - xpath_query q(query, variables); - return select_nodes(q); - } - - PUGI__FN xpath_node_set xml_node::select_nodes(const xpath_query& query) const - { - return query.evaluate_node_set(*this); - } -} - -#endif - -#ifdef __BORLANDC__ -# pragma option pop -#endif - -// Intel C++ does not properly keep warning state for function templates, -// so popping warning state at the end of translation unit leads to warnings in the middle. -#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) -# pragma warning(pop) -#endif - -// Undefine all local macros (makes sure we're not leaking macros in header-only mode) -#undef PUGI__NO_INLINE -#undef PUGI__STATIC_ASSERT -#undef PUGI__DMC_VOLATILE -#undef PUGI__MSVC_CRT_VERSION -#undef PUGI__NS_BEGIN -#undef PUGI__NS_END -#undef PUGI__FN -#undef PUGI__FN_NO_INLINE -#undef PUGI__IS_CHARTYPE_IMPL -#undef PUGI__IS_CHARTYPE -#undef PUGI__IS_CHARTYPEX -#undef PUGI__SKIPWS -#undef PUGI__OPTSET -#undef PUGI__PUSHNODE -#undef PUGI__POPNODE -#undef PUGI__SCANFOR -#undef PUGI__SCANWHILE -#undef PUGI__ENDSEG -#undef PUGI__THROW_ERROR -#undef PUGI__CHECK_ERROR - -#endif - -/** - * Copyright (c) 2006-2012 Arseny Kapoulkine - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ +/** + * pugixml parser - version 1.4 + * -------------------------------------------------------- + * Copyright (C) 2006-2014, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Report bugs and download new versions at http://pugixml.org/ + * + * This library is distributed under the MIT License. See notice at the end + * of this file. + * + * This work is based on the pugxml parser, which is: + * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) + */ + +#ifndef SOURCE_PUGIXML_CPP +#define SOURCE_PUGIXML_CPP + +#include "pugixml.hpp" + +#include +#include +#include +#include + +#ifdef PUGIXML_WCHAR_MODE +# include +#endif + +#ifndef PUGIXML_NO_XPATH +# include +# include +# ifdef PUGIXML_NO_EXCEPTIONS +# include +# endif +#endif + +#ifndef PUGIXML_NO_STL +# include +# include +# include +#endif + +// For placement new +#include + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4127) // conditional expression is constant +# pragma warning(disable: 4324) // structure was padded due to __declspec(align()) +# pragma warning(disable: 4611) // interaction between '_setjmp' and C++ object destruction is non-portable +# pragma warning(disable: 4702) // unreachable code +# pragma warning(disable: 4996) // this function or variable may be unsafe +# pragma warning(disable: 4793) // function compiled as native: presence of '_setjmp' makes a function unmanaged +#endif + +#ifdef __INTEL_COMPILER +# pragma warning(disable: 177) // function was declared but never referenced +# pragma warning(disable: 279) // controlling expression is constant +# pragma warning(disable: 1478 1786) // function was declared "deprecated" +# pragma warning(disable: 1684) // conversion from pointer to same-sized integral type +#endif + +#if defined(__BORLANDC__) && defined(PUGIXML_HEADER_ONLY) +# pragma warn -8080 // symbol is declared but never used; disabling this inside push/pop bracket does not make the warning go away +#endif + +#ifdef __BORLANDC__ +# pragma option push +# pragma warn -8008 // condition is always false +# pragma warn -8066 // unreachable code +#endif + +#ifdef __SNC__ +// Using diag_push/diag_pop does not disable the warnings inside templates due to a compiler bug +# pragma diag_suppress=178 // function was declared but never referenced +# pragma diag_suppress=237 // controlling expression is constant +#endif + +// Inlining controls +#if defined(_MSC_VER) && _MSC_VER >= 1300 +# define PUGI__NO_INLINE __declspec(noinline) +#elif defined(__GNUC__) +# define PUGI__NO_INLINE __attribute__((noinline)) +#else +# define PUGI__NO_INLINE +#endif + +// Simple static assertion +#define PUGI__STATIC_ASSERT(cond) { static const char condition_failed[(cond) ? 1 : -1] = {0}; (void)condition_failed[0]; } + +// Digital Mars C++ bug workaround for passing char loaded from memory via stack +#ifdef __DMC__ +# define PUGI__DMC_VOLATILE volatile +#else +# define PUGI__DMC_VOLATILE +#endif + +// Borland C++ bug workaround for not defining ::memcpy depending on header include order (can't always use std::memcpy because some compilers don't have it at all) +#if defined(__BORLANDC__) && !defined(__MEM_H_USING_LIST) +using std::memcpy; +using std::memmove; +#endif + +// In some environments MSVC is a compiler but the CRT lacks certain MSVC-specific features +#if defined(_MSC_VER) && !defined(__S3E__) +# define PUGI__MSVC_CRT_VERSION _MSC_VER +#endif + +#ifdef PUGIXML_HEADER_ONLY +# define PUGI__NS_BEGIN namespace pugi { namespace impl { +# define PUGI__NS_END } } +# define PUGI__FN inline +# define PUGI__FN_NO_INLINE inline +#else +# if defined(_MSC_VER) && _MSC_VER < 1300 // MSVC6 seems to have an amusing bug with anonymous namespaces inside namespaces +# define PUGI__NS_BEGIN namespace pugi { namespace impl { +# define PUGI__NS_END } } +# else +# define PUGI__NS_BEGIN namespace pugi { namespace impl { namespace { +# define PUGI__NS_END } } } +# endif +# define PUGI__FN +# define PUGI__FN_NO_INLINE PUGI__NO_INLINE +#endif + +// uintptr_t +#if !defined(_MSC_VER) || _MSC_VER >= 1600 +# include +#else +# ifndef _UINTPTR_T_DEFINED +// No native uintptr_t in MSVC6 and in some WinCE versions +typedef size_t uintptr_t; +#define _UINTPTR_T_DEFINED +# endif +PUGI__NS_BEGIN + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + typedef unsigned __int32 uint32_t; +PUGI__NS_END +#endif + +// Memory allocation +PUGI__NS_BEGIN + PUGI__FN void* default_allocate(size_t size) + { + return malloc(size); + } + + PUGI__FN void default_deallocate(void* ptr) + { + free(ptr); + } + + template + struct xml_memory_management_function_storage + { + static allocation_function allocate; + static deallocation_function deallocate; + }; + + template allocation_function xml_memory_management_function_storage::allocate = default_allocate; + template deallocation_function xml_memory_management_function_storage::deallocate = default_deallocate; + + typedef xml_memory_management_function_storage xml_memory; +PUGI__NS_END + +// String utilities +PUGI__NS_BEGIN + // Get string length + PUGI__FN size_t strlength(const char_t* s) + { + assert(s); + + #ifdef PUGIXML_WCHAR_MODE + return wcslen(s); + #else + return strlen(s); + #endif + } + + // Compare two strings + PUGI__FN bool strequal(const char_t* src, const char_t* dst) + { + assert(src && dst); + + #ifdef PUGIXML_WCHAR_MODE + return wcscmp(src, dst) == 0; + #else + return strcmp(src, dst) == 0; + #endif + } + + // Compare lhs with [rhs_begin, rhs_end) + PUGI__FN bool strequalrange(const char_t* lhs, const char_t* rhs, size_t count) + { + for (size_t i = 0; i < count; ++i) + if (lhs[i] != rhs[i]) + return false; + + return lhs[count] == 0; + } + + // Get length of wide string, even if CRT lacks wide character support + PUGI__FN size_t strlength_wide(const wchar_t* s) + { + assert(s); + + #ifdef PUGIXML_WCHAR_MODE + return wcslen(s); + #else + const wchar_t* end = s; + while (*end) end++; + return static_cast(end - s); + #endif + } + +#ifdef PUGIXML_WCHAR_MODE + // Convert string to wide string, assuming all symbols are ASCII + PUGI__FN void widen_ascii(wchar_t* dest, const char* source) + { + for (const char* i = source; *i; ++i) *dest++ = *i; + *dest = 0; + } +#endif +PUGI__NS_END + +#if !defined(PUGIXML_NO_STL) || !defined(PUGIXML_NO_XPATH) +// auto_ptr-like buffer holder for exception recovery +PUGI__NS_BEGIN + struct buffer_holder + { + void* data; + void (*deleter)(void*); + + buffer_holder(void* data_, void (*deleter_)(void*)): data(data_), deleter(deleter_) + { + } + + ~buffer_holder() + { + if (data) deleter(data); + } + + void* release() + { + void* result = data; + data = 0; + return result; + } + }; +PUGI__NS_END +#endif + +PUGI__NS_BEGIN + static const size_t xml_memory_page_size = + #ifdef PUGIXML_MEMORY_PAGE_SIZE + PUGIXML_MEMORY_PAGE_SIZE + #else + 32768 + #endif + ; + + static const uintptr_t xml_memory_page_alignment = 32; + static const uintptr_t xml_memory_page_pointer_mask = ~(xml_memory_page_alignment - 1); + static const uintptr_t xml_memory_page_name_allocated_mask = 16; + static const uintptr_t xml_memory_page_value_allocated_mask = 8; + static const uintptr_t xml_memory_page_type_mask = 7; + + struct xml_allocator; + + struct xml_memory_page + { + static xml_memory_page* construct(void* memory) + { + if (!memory) return 0; //$ redundant, left for performance + + xml_memory_page* result = static_cast(memory); + + result->allocator = 0; + result->memory = 0; + result->prev = 0; + result->next = 0; + result->busy_size = 0; + result->freed_size = 0; + + return result; + } + + xml_allocator* allocator; + + void* memory; + + xml_memory_page* prev; + xml_memory_page* next; + + size_t busy_size; + size_t freed_size; + + char data[1]; + }; + + struct xml_memory_string_header + { + uint16_t page_offset; // offset from page->data + uint16_t full_size; // 0 if string occupies whole page + }; + + struct xml_allocator + { + xml_allocator(xml_memory_page* root): _root(root), _busy_size(root->busy_size) + { + } + + xml_memory_page* allocate_page(size_t data_size) + { + size_t size = offsetof(xml_memory_page, data) + data_size; + + // allocate block with some alignment, leaving memory for worst-case padding + void* memory = xml_memory::allocate(size + xml_memory_page_alignment); + if (!memory) return 0; + + // align upwards to page boundary + void* page_memory = reinterpret_cast((reinterpret_cast(memory) + (xml_memory_page_alignment - 1)) & ~(xml_memory_page_alignment - 1)); + + // prepare page structure + xml_memory_page* page = xml_memory_page::construct(page_memory); + assert(page); + + page->memory = memory; + page->allocator = _root->allocator; + + return page; + } + + static void deallocate_page(xml_memory_page* page) + { + xml_memory::deallocate(page->memory); + } + + void* allocate_memory_oob(size_t size, xml_memory_page*& out_page); + + void* allocate_memory(size_t size, xml_memory_page*& out_page) + { + if (_busy_size + size > xml_memory_page_size) return allocate_memory_oob(size, out_page); + + void* buf = _root->data + _busy_size; + + _busy_size += size; + + out_page = _root; + + return buf; + } + + void deallocate_memory(void* ptr, size_t size, xml_memory_page* page) + { + if (page == _root) page->busy_size = _busy_size; + + assert(ptr >= page->data && ptr < page->data + page->busy_size); + (void)!ptr; + + page->freed_size += size; + assert(page->freed_size <= page->busy_size); + + if (page->freed_size == page->busy_size) + { + if (page->next == 0) + { + assert(_root == page); + + // top page freed, just reset sizes + page->busy_size = page->freed_size = 0; + _busy_size = 0; + } + else + { + assert(_root != page); + assert(page->prev); + + // remove from the list + page->prev->next = page->next; + page->next->prev = page->prev; + + // deallocate + deallocate_page(page); + } + } + } + + char_t* allocate_string(size_t length) + { + // allocate memory for string and header block + size_t size = sizeof(xml_memory_string_header) + length * sizeof(char_t); + + // round size up to pointer alignment boundary + size_t full_size = (size + (sizeof(void*) - 1)) & ~(sizeof(void*) - 1); + + xml_memory_page* page; + xml_memory_string_header* header = static_cast(allocate_memory(full_size, page)); + + if (!header) return 0; + + // setup header + ptrdiff_t page_offset = reinterpret_cast(header) - page->data; + + assert(page_offset >= 0 && page_offset < (1 << 16)); + header->page_offset = static_cast(page_offset); + + // full_size == 0 for large strings that occupy the whole page + assert(full_size < (1 << 16) || (page->busy_size == full_size && page_offset == 0)); + header->full_size = static_cast(full_size < (1 << 16) ? full_size : 0); + + // round-trip through void* to avoid 'cast increases required alignment of target type' warning + // header is guaranteed a pointer-sized alignment, which should be enough for char_t + return static_cast(static_cast(header + 1)); + } + + void deallocate_string(char_t* string) + { + // this function casts pointers through void* to avoid 'cast increases required alignment of target type' warnings + // we're guaranteed the proper (pointer-sized) alignment on the input string if it was allocated via allocate_string + + // get header + xml_memory_string_header* header = static_cast(static_cast(string)) - 1; + + // deallocate + size_t page_offset = offsetof(xml_memory_page, data) + header->page_offset; + xml_memory_page* page = reinterpret_cast(static_cast(reinterpret_cast(header) - page_offset)); + + // if full_size == 0 then this string occupies the whole page + size_t full_size = header->full_size == 0 ? page->busy_size : header->full_size; + + deallocate_memory(header, full_size, page); + } + + xml_memory_page* _root; + size_t _busy_size; + }; + + PUGI__FN_NO_INLINE void* xml_allocator::allocate_memory_oob(size_t size, xml_memory_page*& out_page) + { + const size_t large_allocation_threshold = xml_memory_page_size / 4; + + xml_memory_page* page = allocate_page(size <= large_allocation_threshold ? xml_memory_page_size : size); + out_page = page; + + if (!page) return 0; + + if (size <= large_allocation_threshold) + { + _root->busy_size = _busy_size; + + // insert page at the end of linked list + page->prev = _root; + _root->next = page; + _root = page; + + _busy_size = size; + } + else + { + // insert page before the end of linked list, so that it is deleted as soon as possible + // the last page is not deleted even if it's empty (see deallocate_memory) + assert(_root->prev); + + page->prev = _root->prev; + page->next = _root; + + _root->prev->next = page; + _root->prev = page; + } + + // allocate inside page + page->busy_size = size; + + return page->data; + } +PUGI__NS_END + +namespace pugi +{ + /// A 'name=value' XML attribute structure. + struct xml_attribute_struct + { + /// Default ctor + xml_attribute_struct(impl::xml_memory_page* page): header(reinterpret_cast(page)), name(0), value(0), prev_attribute_c(0), next_attribute(0) + { + } + + uintptr_t header; + + char_t* name; ///< Pointer to attribute name. + char_t* value; ///< Pointer to attribute value. + + xml_attribute_struct* prev_attribute_c; ///< Previous attribute (cyclic list) + xml_attribute_struct* next_attribute; ///< Next attribute + }; + + /// An XML document tree node. + struct xml_node_struct + { + /// Default ctor + /// \param type - node type + xml_node_struct(impl::xml_memory_page* page, xml_node_type type): header(reinterpret_cast(page) | (type - 1)), parent(0), name(0), value(0), first_child(0), prev_sibling_c(0), next_sibling(0), first_attribute(0) + { + } + + uintptr_t header; + + xml_node_struct* parent; ///< Pointer to parent + + char_t* name; ///< Pointer to element name. + char_t* value; ///< Pointer to any associated string data. + + xml_node_struct* first_child; ///< First child + + xml_node_struct* prev_sibling_c; ///< Left brother (cyclic list) + xml_node_struct* next_sibling; ///< Right brother + + xml_attribute_struct* first_attribute; ///< First attribute + }; +} + +PUGI__NS_BEGIN + struct xml_extra_buffer + { + char_t* buffer; + xml_extra_buffer* next; + }; + + struct xml_document_struct: public xml_node_struct, public xml_allocator + { + xml_document_struct(xml_memory_page* page): xml_node_struct(page, node_document), xml_allocator(page), buffer(0), extra_buffers(0) + { + } + + const char_t* buffer; + + xml_extra_buffer* extra_buffers; + }; + + inline xml_allocator& get_allocator(const xml_node_struct* node) + { + assert(node); + + return *reinterpret_cast(node->header & xml_memory_page_pointer_mask)->allocator; + } +PUGI__NS_END + +// Low-level DOM operations +PUGI__NS_BEGIN + inline xml_attribute_struct* allocate_attribute(xml_allocator& alloc) + { + xml_memory_page* page; + void* memory = alloc.allocate_memory(sizeof(xml_attribute_struct), page); + + return new (memory) xml_attribute_struct(page); + } + + inline xml_node_struct* allocate_node(xml_allocator& alloc, xml_node_type type) + { + xml_memory_page* page; + void* memory = alloc.allocate_memory(sizeof(xml_node_struct), page); + + return new (memory) xml_node_struct(page, type); + } + + inline void destroy_attribute(xml_attribute_struct* a, xml_allocator& alloc) + { + uintptr_t header = a->header; + + if (header & impl::xml_memory_page_name_allocated_mask) alloc.deallocate_string(a->name); + if (header & impl::xml_memory_page_value_allocated_mask) alloc.deallocate_string(a->value); + + alloc.deallocate_memory(a, sizeof(xml_attribute_struct), reinterpret_cast(header & xml_memory_page_pointer_mask)); + } + + inline void destroy_node(xml_node_struct* n, xml_allocator& alloc) + { + uintptr_t header = n->header; + + if (header & impl::xml_memory_page_name_allocated_mask) alloc.deallocate_string(n->name); + if (header & impl::xml_memory_page_value_allocated_mask) alloc.deallocate_string(n->value); + + for (xml_attribute_struct* attr = n->first_attribute; attr; ) + { + xml_attribute_struct* next = attr->next_attribute; + + destroy_attribute(attr, alloc); + + attr = next; + } + + for (xml_node_struct* child = n->first_child; child; ) + { + xml_node_struct* next = child->next_sibling; + + destroy_node(child, alloc); + + child = next; + } + + alloc.deallocate_memory(n, sizeof(xml_node_struct), reinterpret_cast(header & xml_memory_page_pointer_mask)); + } + + PUGI__FN_NO_INLINE xml_node_struct* append_node(xml_node_struct* node, xml_allocator& alloc, xml_node_type type = node_element) + { + xml_node_struct* child = allocate_node(alloc, type); + if (!child) return 0; + + child->parent = node; + + xml_node_struct* first_child = node->first_child; + + if (first_child) + { + xml_node_struct* last_child = first_child->prev_sibling_c; + + last_child->next_sibling = child; + child->prev_sibling_c = last_child; + first_child->prev_sibling_c = child; + } + else + { + node->first_child = child; + child->prev_sibling_c = child; + } + + return child; + } + + PUGI__FN_NO_INLINE xml_attribute_struct* append_attribute_ll(xml_node_struct* node, xml_allocator& alloc) + { + xml_attribute_struct* a = allocate_attribute(alloc); + if (!a) return 0; + + xml_attribute_struct* first_attribute = node->first_attribute; + + if (first_attribute) + { + xml_attribute_struct* last_attribute = first_attribute->prev_attribute_c; + + last_attribute->next_attribute = a; + a->prev_attribute_c = last_attribute; + first_attribute->prev_attribute_c = a; + } + else + { + node->first_attribute = a; + a->prev_attribute_c = a; + } + + return a; + } +PUGI__NS_END + +// Helper classes for code generation +PUGI__NS_BEGIN + struct opt_false + { + enum { value = 0 }; + }; + + struct opt_true + { + enum { value = 1 }; + }; +PUGI__NS_END + +// Unicode utilities +PUGI__NS_BEGIN + inline uint16_t endian_swap(uint16_t value) + { + return static_cast(((value & 0xff) << 8) | (value >> 8)); + } + + inline uint32_t endian_swap(uint32_t value) + { + return ((value & 0xff) << 24) | ((value & 0xff00) << 8) | ((value & 0xff0000) >> 8) | (value >> 24); + } + + struct utf8_counter + { + typedef size_t value_type; + + static value_type low(value_type result, uint32_t ch) + { + // U+0000..U+007F + if (ch < 0x80) return result + 1; + // U+0080..U+07FF + else if (ch < 0x800) return result + 2; + // U+0800..U+FFFF + else return result + 3; + } + + static value_type high(value_type result, uint32_t) + { + // U+10000..U+10FFFF + return result + 4; + } + }; + + struct utf8_writer + { + typedef uint8_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + // U+0000..U+007F + if (ch < 0x80) + { + *result = static_cast(ch); + return result + 1; + } + // U+0080..U+07FF + else if (ch < 0x800) + { + result[0] = static_cast(0xC0 | (ch >> 6)); + result[1] = static_cast(0x80 | (ch & 0x3F)); + return result + 2; + } + // U+0800..U+FFFF + else + { + result[0] = static_cast(0xE0 | (ch >> 12)); + result[1] = static_cast(0x80 | ((ch >> 6) & 0x3F)); + result[2] = static_cast(0x80 | (ch & 0x3F)); + return result + 3; + } + } + + static value_type high(value_type result, uint32_t ch) + { + // U+10000..U+10FFFF + result[0] = static_cast(0xF0 | (ch >> 18)); + result[1] = static_cast(0x80 | ((ch >> 12) & 0x3F)); + result[2] = static_cast(0x80 | ((ch >> 6) & 0x3F)); + result[3] = static_cast(0x80 | (ch & 0x3F)); + return result + 4; + } + + static value_type any(value_type result, uint32_t ch) + { + return (ch < 0x10000) ? low(result, ch) : high(result, ch); + } + }; + + struct utf16_counter + { + typedef size_t value_type; + + static value_type low(value_type result, uint32_t) + { + return result + 1; + } + + static value_type high(value_type result, uint32_t) + { + return result + 2; + } + }; + + struct utf16_writer + { + typedef uint16_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + *result = static_cast(ch); + + return result + 1; + } + + static value_type high(value_type result, uint32_t ch) + { + uint32_t msh = static_cast(ch - 0x10000) >> 10; + uint32_t lsh = static_cast(ch - 0x10000) & 0x3ff; + + result[0] = static_cast(0xD800 + msh); + result[1] = static_cast(0xDC00 + lsh); + + return result + 2; + } + + static value_type any(value_type result, uint32_t ch) + { + return (ch < 0x10000) ? low(result, ch) : high(result, ch); + } + }; + + struct utf32_counter + { + typedef size_t value_type; + + static value_type low(value_type result, uint32_t) + { + return result + 1; + } + + static value_type high(value_type result, uint32_t) + { + return result + 1; + } + }; + + struct utf32_writer + { + typedef uint32_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + *result = ch; + + return result + 1; + } + + static value_type high(value_type result, uint32_t ch) + { + *result = ch; + + return result + 1; + } + + static value_type any(value_type result, uint32_t ch) + { + *result = ch; + + return result + 1; + } + }; + + struct latin1_writer + { + typedef uint8_t* value_type; + + static value_type low(value_type result, uint32_t ch) + { + *result = static_cast(ch > 255 ? '?' : ch); + + return result + 1; + } + + static value_type high(value_type result, uint32_t ch) + { + (void)ch; + + *result = '?'; + + return result + 1; + } + }; + + template struct wchar_selector; + + template <> struct wchar_selector<2> + { + typedef uint16_t type; + typedef utf16_counter counter; + typedef utf16_writer writer; + }; + + template <> struct wchar_selector<4> + { + typedef uint32_t type; + typedef utf32_counter counter; + typedef utf32_writer writer; + }; + + typedef wchar_selector::counter wchar_counter; + typedef wchar_selector::writer wchar_writer; + + template struct utf_decoder + { + static inline typename Traits::value_type decode_utf8_block(const uint8_t* data, size_t size, typename Traits::value_type result) + { + const uint8_t utf8_byte_mask = 0x3f; + + while (size) + { + uint8_t lead = *data; + + // 0xxxxxxx -> U+0000..U+007F + if (lead < 0x80) + { + result = Traits::low(result, lead); + data += 1; + size -= 1; + + // process aligned single-byte (ascii) blocks + if ((reinterpret_cast(data) & 3) == 0) + { + // round-trip through void* to silence 'cast increases required alignment of target type' warnings + while (size >= 4 && (*static_cast(static_cast(data)) & 0x80808080) == 0) + { + result = Traits::low(result, data[0]); + result = Traits::low(result, data[1]); + result = Traits::low(result, data[2]); + result = Traits::low(result, data[3]); + data += 4; + size -= 4; + } + } + } + // 110xxxxx -> U+0080..U+07FF + else if (static_cast(lead - 0xC0) < 0x20 && size >= 2 && (data[1] & 0xc0) == 0x80) + { + result = Traits::low(result, ((lead & ~0xC0) << 6) | (data[1] & utf8_byte_mask)); + data += 2; + size -= 2; + } + // 1110xxxx -> U+0800-U+FFFF + else if (static_cast(lead - 0xE0) < 0x10 && size >= 3 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80) + { + result = Traits::low(result, ((lead & ~0xE0) << 12) | ((data[1] & utf8_byte_mask) << 6) | (data[2] & utf8_byte_mask)); + data += 3; + size -= 3; + } + // 11110xxx -> U+10000..U+10FFFF + else if (static_cast(lead - 0xF0) < 0x08 && size >= 4 && (data[1] & 0xc0) == 0x80 && (data[2] & 0xc0) == 0x80 && (data[3] & 0xc0) == 0x80) + { + result = Traits::high(result, ((lead & ~0xF0) << 18) | ((data[1] & utf8_byte_mask) << 12) | ((data[2] & utf8_byte_mask) << 6) | (data[3] & utf8_byte_mask)); + data += 4; + size -= 4; + } + // 10xxxxxx or 11111xxx -> invalid + else + { + data += 1; + size -= 1; + } + } + + return result; + } + + static inline typename Traits::value_type decode_utf16_block(const uint16_t* data, size_t size, typename Traits::value_type result) + { + const uint16_t* end = data + size; + + while (data < end) + { + unsigned int lead = opt_swap::value ? endian_swap(*data) : *data; + + // U+0000..U+D7FF + if (lead < 0xD800) + { + result = Traits::low(result, lead); + data += 1; + } + // U+E000..U+FFFF + else if (static_cast(lead - 0xE000) < 0x2000) + { + result = Traits::low(result, lead); + data += 1; + } + // surrogate pair lead + else if (static_cast(lead - 0xD800) < 0x400 && data + 1 < end) + { + uint16_t next = opt_swap::value ? endian_swap(data[1]) : data[1]; + + if (static_cast(next - 0xDC00) < 0x400) + { + result = Traits::high(result, 0x10000 + ((lead & 0x3ff) << 10) + (next & 0x3ff)); + data += 2; + } + else + { + data += 1; + } + } + else + { + data += 1; + } + } + + return result; + } + + static inline typename Traits::value_type decode_utf32_block(const uint32_t* data, size_t size, typename Traits::value_type result) + { + const uint32_t* end = data + size; + + while (data < end) + { + uint32_t lead = opt_swap::value ? endian_swap(*data) : *data; + + // U+0000..U+FFFF + if (lead < 0x10000) + { + result = Traits::low(result, lead); + data += 1; + } + // U+10000..U+10FFFF + else + { + result = Traits::high(result, lead); + data += 1; + } + } + + return result; + } + + static inline typename Traits::value_type decode_latin1_block(const uint8_t* data, size_t size, typename Traits::value_type result) + { + for (size_t i = 0; i < size; ++i) + { + result = Traits::low(result, data[i]); + } + + return result; + } + + static inline typename Traits::value_type decode_wchar_block_impl(const uint16_t* data, size_t size, typename Traits::value_type result) + { + return decode_utf16_block(data, size, result); + } + + static inline typename Traits::value_type decode_wchar_block_impl(const uint32_t* data, size_t size, typename Traits::value_type result) + { + return decode_utf32_block(data, size, result); + } + + static inline typename Traits::value_type decode_wchar_block(const wchar_t* data, size_t size, typename Traits::value_type result) + { + return decode_wchar_block_impl(reinterpret_cast::type*>(data), size, result); + } + }; + + template PUGI__FN void convert_utf_endian_swap(T* result, const T* data, size_t length) + { + for (size_t i = 0; i < length; ++i) result[i] = endian_swap(data[i]); + } + +#ifdef PUGIXML_WCHAR_MODE + PUGI__FN void convert_wchar_endian_swap(wchar_t* result, const wchar_t* data, size_t length) + { + for (size_t i = 0; i < length; ++i) result[i] = static_cast(endian_swap(static_cast::type>(data[i]))); + } +#endif +PUGI__NS_END + +PUGI__NS_BEGIN + enum chartype_t + { + ct_parse_pcdata = 1, // \0, &, \r, < + ct_parse_attr = 2, // \0, &, \r, ', " + ct_parse_attr_ws = 4, // \0, &, \r, ', ", \n, tab + ct_space = 8, // \r, \n, space, tab + ct_parse_cdata = 16, // \0, ], >, \r + ct_parse_comment = 32, // \0, -, >, \r + ct_symbol = 64, // Any symbol > 127, a-z, A-Z, 0-9, _, :, -, . + ct_start_symbol = 128 // Any symbol > 127, a-z, A-Z, _, : + }; + + static const unsigned char chartype_table[256] = + { + 55, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 63, 0, 0, // 0-15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31 + 8, 0, 6, 0, 0, 0, 7, 6, 0, 0, 0, 0, 0, 96, 64, 0, // 32-47 + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 192, 0, 1, 0, 48, 0, // 48-63 + 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 64-79 + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 16, 0, 192, // 80-95 + 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 96-111 + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 0, 0, 0, 0, // 112-127 + + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, // 128+ + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192 + }; + + enum chartypex_t + { + ctx_special_pcdata = 1, // Any symbol >= 0 and < 32 (except \t, \r, \n), &, <, > + ctx_special_attr = 2, // Any symbol >= 0 and < 32 (except \t), &, <, >, " + ctx_start_symbol = 4, // Any symbol > 127, a-z, A-Z, _ + ctx_digit = 8, // 0-9 + ctx_symbol = 16 // Any symbol > 127, a-z, A-Z, 0-9, _, -, . + }; + + static const unsigned char chartypex_table[256] = + { + 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 2, 3, 3, 2, 3, 3, // 0-15 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 16-31 + 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 16, 16, 0, // 32-47 + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 0, 0, 3, 0, 3, 0, // 48-63 + + 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 64-79 + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 20, // 80-95 + 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 96-111 + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0, 0, 0, // 112-127 + + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, // 128+ + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 + }; + +#ifdef PUGIXML_WCHAR_MODE + #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) ((static_cast(c) < 128 ? table[static_cast(c)] : table[128]) & (ct)) +#else + #define PUGI__IS_CHARTYPE_IMPL(c, ct, table) (table[static_cast(c)] & (ct)) +#endif + + #define PUGI__IS_CHARTYPE(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartype_table) + #define PUGI__IS_CHARTYPEX(c, ct) PUGI__IS_CHARTYPE_IMPL(c, ct, chartypex_table) + + PUGI__FN bool is_little_endian() + { + unsigned int ui = 1; + + return *reinterpret_cast(&ui) == 1; + } + + PUGI__FN xml_encoding get_wchar_encoding() + { + PUGI__STATIC_ASSERT(sizeof(wchar_t) == 2 || sizeof(wchar_t) == 4); + + if (sizeof(wchar_t) == 2) + return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + else + return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + } + + PUGI__FN xml_encoding guess_buffer_encoding(uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3) + { + // look for BOM in first few bytes + if (d0 == 0 && d1 == 0 && d2 == 0xfe && d3 == 0xff) return encoding_utf32_be; + if (d0 == 0xff && d1 == 0xfe && d2 == 0 && d3 == 0) return encoding_utf32_le; + if (d0 == 0xfe && d1 == 0xff) return encoding_utf16_be; + if (d0 == 0xff && d1 == 0xfe) return encoding_utf16_le; + if (d0 == 0xef && d1 == 0xbb && d2 == 0xbf) return encoding_utf8; + + // look for <, (contents); + + PUGI__DMC_VOLATILE uint8_t d0 = data[0], d1 = data[1], d2 = data[2], d3 = data[3]; + + return guess_buffer_encoding(d0, d1, d2, d3); + } + + PUGI__FN bool get_mutable_buffer(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) + { + size_t length = size / sizeof(char_t); + + if (is_mutable) + { + out_buffer = static_cast(const_cast(contents)); + out_length = length; + } + else + { + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + memcpy(buffer, contents, length * sizeof(char_t)); + buffer[length] = 0; + + out_buffer = buffer; + out_length = length + 1; + } + + return true; + } + +#ifdef PUGIXML_WCHAR_MODE + PUGI__FN bool need_endian_swap_utf(xml_encoding le, xml_encoding re) + { + return (le == encoding_utf16_be && re == encoding_utf16_le) || (le == encoding_utf16_le && re == encoding_utf16_be) || + (le == encoding_utf32_be && re == encoding_utf32_le) || (le == encoding_utf32_le && re == encoding_utf32_be); + } + + PUGI__FN bool convert_buffer_endian_swap(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) + { + const char_t* data = static_cast(contents); + size_t length = size / sizeof(char_t); + + if (is_mutable) + { + char_t* buffer = const_cast(data); + + convert_wchar_endian_swap(buffer, data, length); + + out_buffer = buffer; + out_length = length; + } + else + { + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + convert_wchar_endian_swap(buffer, data, length); + buffer[length] = 0; + + out_buffer = buffer; + out_length = length + 1; + } + + return true; + } + + PUGI__FN bool convert_buffer_utf8(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size) + { + const uint8_t* data = static_cast(contents); + size_t data_length = size; + + // first pass: get length in wchar_t units + size_t length = utf_decoder::decode_utf8_block(data, data_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf8 input to wchar_t + wchar_writer::value_type obegin = reinterpret_cast(buffer); + wchar_writer::value_type oend = utf_decoder::decode_utf8_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + template PUGI__FN bool convert_buffer_utf16(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) + { + const uint16_t* data = static_cast(contents); + size_t data_length = size / sizeof(uint16_t); + + // first pass: get length in wchar_t units + size_t length = utf_decoder::decode_utf16_block(data, data_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf16 input to wchar_t + wchar_writer::value_type obegin = reinterpret_cast(buffer); + wchar_writer::value_type oend = utf_decoder::decode_utf16_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + template PUGI__FN bool convert_buffer_utf32(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) + { + const uint32_t* data = static_cast(contents); + size_t data_length = size / sizeof(uint32_t); + + // first pass: get length in wchar_t units + size_t length = utf_decoder::decode_utf32_block(data, data_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf32 input to wchar_t + wchar_writer::value_type obegin = reinterpret_cast(buffer); + wchar_writer::value_type oend = utf_decoder::decode_utf32_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size) + { + const uint8_t* data = static_cast(contents); + size_t data_length = size; + + // get length in wchar_t units + size_t length = data_length; + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // convert latin1 input to wchar_t + wchar_writer::value_type obegin = reinterpret_cast(buffer); + wchar_writer::value_type oend = utf_decoder::decode_latin1_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) + { + // get native encoding + xml_encoding wchar_encoding = get_wchar_encoding(); + + // fast path: no conversion required + if (encoding == wchar_encoding) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + + // only endian-swapping is required + if (need_endian_swap_utf(encoding, wchar_encoding)) return convert_buffer_endian_swap(out_buffer, out_length, contents, size, is_mutable); + + // source encoding is utf8 + if (encoding == encoding_utf8) return convert_buffer_utf8(out_buffer, out_length, contents, size); + + // source encoding is utf16 + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + return (native_encoding == encoding) ? + convert_buffer_utf16(out_buffer, out_length, contents, size, opt_false()) : + convert_buffer_utf16(out_buffer, out_length, contents, size, opt_true()); + } + + // source encoding is utf32 + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + return (native_encoding == encoding) ? + convert_buffer_utf32(out_buffer, out_length, contents, size, opt_false()) : + convert_buffer_utf32(out_buffer, out_length, contents, size, opt_true()); + } + + // source encoding is latin1 + if (encoding == encoding_latin1) return convert_buffer_latin1(out_buffer, out_length, contents, size); + + assert(!"Invalid encoding"); + return false; + } +#else + template PUGI__FN bool convert_buffer_utf16(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) + { + const uint16_t* data = static_cast(contents); + size_t data_length = size / sizeof(uint16_t); + + // first pass: get length in utf8 units + size_t length = utf_decoder::decode_utf16_block(data, data_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf16 input to utf8 + uint8_t* obegin = reinterpret_cast(buffer); + uint8_t* oend = utf_decoder::decode_utf16_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + template PUGI__FN bool convert_buffer_utf32(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, opt_swap) + { + const uint32_t* data = static_cast(contents); + size_t data_length = size / sizeof(uint32_t); + + // first pass: get length in utf8 units + size_t length = utf_decoder::decode_utf32_block(data, data_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert utf32 input to utf8 + uint8_t* obegin = reinterpret_cast(buffer); + uint8_t* oend = utf_decoder::decode_utf32_block(data, data_length, obegin); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + PUGI__FN size_t get_latin1_7bit_prefix_length(const uint8_t* data, size_t size) + { + for (size_t i = 0; i < size; ++i) + if (data[i] > 127) + return i; + + return size; + } + + PUGI__FN bool convert_buffer_latin1(char_t*& out_buffer, size_t& out_length, const void* contents, size_t size, bool is_mutable) + { + const uint8_t* data = static_cast(contents); + size_t data_length = size; + + // get size of prefix that does not need utf8 conversion + size_t prefix_length = get_latin1_7bit_prefix_length(data, data_length); + assert(prefix_length <= data_length); + + const uint8_t* postfix = data + prefix_length; + size_t postfix_length = data_length - prefix_length; + + // if no conversion is needed, just return the original buffer + if (postfix_length == 0) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + + // first pass: get length in utf8 units + size_t length = prefix_length + utf_decoder::decode_latin1_block(postfix, postfix_length, 0); + + // allocate buffer of suitable length + char_t* buffer = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!buffer) return false; + + // second pass: convert latin1 input to utf8 + memcpy(buffer, data, prefix_length); + + uint8_t* obegin = reinterpret_cast(buffer); + uint8_t* oend = utf_decoder::decode_latin1_block(postfix, postfix_length, obegin + prefix_length); + + assert(oend == obegin + length); + *oend = 0; + + out_buffer = buffer; + out_length = length + 1; + + return true; + } + + PUGI__FN bool convert_buffer(char_t*& out_buffer, size_t& out_length, xml_encoding encoding, const void* contents, size_t size, bool is_mutable) + { + // fast path: no conversion required + if (encoding == encoding_utf8) return get_mutable_buffer(out_buffer, out_length, contents, size, is_mutable); + + // source encoding is utf16 + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + return (native_encoding == encoding) ? + convert_buffer_utf16(out_buffer, out_length, contents, size, opt_false()) : + convert_buffer_utf16(out_buffer, out_length, contents, size, opt_true()); + } + + // source encoding is utf32 + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + return (native_encoding == encoding) ? + convert_buffer_utf32(out_buffer, out_length, contents, size, opt_false()) : + convert_buffer_utf32(out_buffer, out_length, contents, size, opt_true()); + } + + // source encoding is latin1 + if (encoding == encoding_latin1) return convert_buffer_latin1(out_buffer, out_length, contents, size, is_mutable); + + assert(!"Invalid encoding"); + return false; + } +#endif + + PUGI__FN size_t as_utf8_begin(const wchar_t* str, size_t length) + { + // get length in utf8 characters + return utf_decoder::decode_wchar_block(str, length, 0); + } + + PUGI__FN void as_utf8_end(char* buffer, size_t size, const wchar_t* str, size_t length) + { + // convert to utf8 + uint8_t* begin = reinterpret_cast(buffer); + uint8_t* end = utf_decoder::decode_wchar_block(str, length, begin); + + assert(begin + size == end); + (void)!end; + + // zero-terminate + buffer[size] = 0; + } + +#ifndef PUGIXML_NO_STL + PUGI__FN std::string as_utf8_impl(const wchar_t* str, size_t length) + { + // first pass: get length in utf8 characters + size_t size = as_utf8_begin(str, length); + + // allocate resulting string + std::string result; + result.resize(size); + + // second pass: convert to utf8 + if (size > 0) as_utf8_end(&result[0], size, str, length); + + return result; + } + + PUGI__FN std::basic_string as_wide_impl(const char* str, size_t size) + { + const uint8_t* data = reinterpret_cast(str); + + // first pass: get length in wchar_t units + size_t length = utf_decoder::decode_utf8_block(data, size, 0); + + // allocate resulting string + std::basic_string result; + result.resize(length); + + // second pass: convert to wchar_t + if (length > 0) + { + wchar_writer::value_type begin = reinterpret_cast(&result[0]); + wchar_writer::value_type end = utf_decoder::decode_utf8_block(data, size, begin); + + assert(begin + length == end); + (void)!end; + } + + return result; + } +#endif + + inline bool strcpy_insitu_allow(size_t length, uintptr_t allocated, char_t* target) + { + assert(target); + size_t target_length = strlength(target); + + // always reuse document buffer memory if possible + if (!allocated) return target_length >= length; + + // reuse heap memory if waste is not too great + const size_t reuse_threshold = 32; + + return target_length >= length && (target_length < reuse_threshold || target_length - length < target_length / 2); + } + + PUGI__FN bool strcpy_insitu(char_t*& dest, uintptr_t& header, uintptr_t header_mask, const char_t* source) + { + assert(header); + + size_t source_length = strlength(source); + + if (source_length == 0) + { + // empty string and null pointer are equivalent, so just deallocate old memory + xml_allocator* alloc = reinterpret_cast(header & xml_memory_page_pointer_mask)->allocator; + + if (header & header_mask) alloc->deallocate_string(dest); + + // mark the string as not allocated + dest = 0; + header &= ~header_mask; + + return true; + } + else if (dest && strcpy_insitu_allow(source_length, header & header_mask, dest)) + { + // we can reuse old buffer, so just copy the new data (including zero terminator) + memcpy(dest, source, (source_length + 1) * sizeof(char_t)); + + return true; + } + else + { + xml_allocator* alloc = reinterpret_cast(header & xml_memory_page_pointer_mask)->allocator; + + // allocate new buffer + char_t* buf = alloc->allocate_string(source_length + 1); + if (!buf) return false; + + // copy the string (including zero terminator) + memcpy(buf, source, (source_length + 1) * sizeof(char_t)); + + // deallocate old buffer (*after* the above to protect against overlapping memory and/or allocation failures) + if (header & header_mask) alloc->deallocate_string(dest); + + // the string is now allocated, so set the flag + dest = buf; + header |= header_mask; + + return true; + } + } + + struct gap + { + char_t* end; + size_t size; + + gap(): end(0), size(0) + { + } + + // Push new gap, move s count bytes further (skipping the gap). + // Collapse previous gap. + void push(char_t*& s, size_t count) + { + if (end) // there was a gap already; collapse it + { + // Move [old_gap_end, new_gap_start) to [old_gap_start, ...) + assert(s >= end); + memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); + } + + s += count; // end of current gap + + // "merge" two gaps + end = s; + size += count; + } + + // Collapse all gaps, return past-the-end pointer + char_t* flush(char_t* s) + { + if (end) + { + // Move [old_gap_end, current_pos) to [old_gap_start, ...) + assert(s >= end); + memmove(end - size, end, reinterpret_cast(s) - reinterpret_cast(end)); + + return s - size; + } + else return s; + } + }; + + PUGI__FN char_t* strconv_escape(char_t* s, gap& g) + { + char_t* stre = s + 1; + + switch (*stre) + { + case '#': // &#... + { + unsigned int ucsc = 0; + + if (stre[1] == 'x') // &#x... (hex code) + { + stre += 2; + + char_t ch = *stre; + + if (ch == ';') return stre; + + for (;;) + { + if (static_cast(ch - '0') <= 9) + ucsc = 16 * ucsc + (ch - '0'); + else if (static_cast((ch | ' ') - 'a') <= 5) + ucsc = 16 * ucsc + ((ch | ' ') - 'a' + 10); + else if (ch == ';') + break; + else // cancel + return stre; + + ch = *++stre; + } + + ++stre; + } + else // &#... (dec code) + { + char_t ch = *++stre; + + if (ch == ';') return stre; + + for (;;) + { + if (static_cast(static_cast(ch) - '0') <= 9) + ucsc = 10 * ucsc + (ch - '0'); + else if (ch == ';') + break; + else // cancel + return stre; + + ch = *++stre; + } + + ++stre; + } + + #ifdef PUGIXML_WCHAR_MODE + s = reinterpret_cast(wchar_writer::any(reinterpret_cast(s), ucsc)); + #else + s = reinterpret_cast(utf8_writer::any(reinterpret_cast(s), ucsc)); + #endif + + g.push(s, stre - s); + return stre; + } + + case 'a': // &a + { + ++stre; + + if (*stre == 'm') // &am + { + if (*++stre == 'p' && *++stre == ';') // & + { + *s++ = '&'; + ++stre; + + g.push(s, stre - s); + return stre; + } + } + else if (*stre == 'p') // &ap + { + if (*++stre == 'o' && *++stre == 's' && *++stre == ';') // ' + { + *s++ = '\''; + ++stre; + + g.push(s, stre - s); + return stre; + } + } + break; + } + + case 'g': // &g + { + if (*++stre == 't' && *++stre == ';') // > + { + *s++ = '>'; + ++stre; + + g.push(s, stre - s); + return stre; + } + break; + } + + case 'l': // &l + { + if (*++stre == 't' && *++stre == ';') // < + { + *s++ = '<'; + ++stre; + + g.push(s, stre - s); + return stre; + } + break; + } + + case 'q': // &q + { + if (*++stre == 'u' && *++stre == 'o' && *++stre == 't' && *++stre == ';') // " + { + *s++ = '"'; + ++stre; + + g.push(s, stre - s); + return stre; + } + break; + } + + default: + break; + } + + return stre; + } + + // Utility macro for last character handling + #define ENDSWITH(c, e) ((c) == (e) || ((c) == 0 && endch == (e))) + + PUGI__FN char_t* strconv_comment(char_t* s, char_t endch) + { + gap g; + + while (true) + { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_comment)) ++s; + + if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair + { + *s++ = '\n'; // replace first one with 0x0a + + if (*s == '\n') g.push(s, 1); + } + else if (s[0] == '-' && s[1] == '-' && ENDSWITH(s[2], '>')) // comment ends here + { + *g.flush(s) = 0; + + return s + (s[2] == '>' ? 3 : 2); + } + else if (*s == 0) + { + return 0; + } + else ++s; + } + } + + PUGI__FN char_t* strconv_cdata(char_t* s, char_t endch) + { + gap g; + + while (true) + { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_cdata)) ++s; + + if (*s == '\r') // Either a single 0x0d or 0x0d 0x0a pair + { + *s++ = '\n'; // replace first one with 0x0a + + if (*s == '\n') g.push(s, 1); + } + else if (s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>')) // CDATA ends here + { + *g.flush(s) = 0; + + return s + 1; + } + else if (*s == 0) + { + return 0; + } + else ++s; + } + } + + typedef char_t* (*strconv_pcdata_t)(char_t*); + + template struct strconv_pcdata_impl + { + static char_t* parse(char_t* s) + { + gap g; + + char_t* begin = s; + + while (true) + { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_pcdata)) ++s; + + if (*s == '<') // PCDATA ends here + { + char_t* end = g.flush(s); + + if (opt_trim::value) + while (end > begin && PUGI__IS_CHARTYPE(end[-1], ct_space)) + --end; + + *end = 0; + + return s + 1; + } + else if (opt_eol::value && *s == '\r') // Either a single 0x0d or 0x0d 0x0a pair + { + *s++ = '\n'; // replace first one with 0x0a + + if (*s == '\n') g.push(s, 1); + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (*s == 0) + { + char_t* end = g.flush(s); + + if (opt_trim::value) + while (end > begin && PUGI__IS_CHARTYPE(end[-1], ct_space)) + --end; + + *end = 0; + + return s; + } + else ++s; + } + } + }; + + PUGI__FN strconv_pcdata_t get_strconv_pcdata(unsigned int optmask) + { + PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_trim_pcdata == 0x0800); + + switch (((optmask >> 4) & 3) | ((optmask >> 9) & 4)) // get bitmask for flags (eol escapes trim) + { + case 0: return strconv_pcdata_impl::parse; + case 1: return strconv_pcdata_impl::parse; + case 2: return strconv_pcdata_impl::parse; + case 3: return strconv_pcdata_impl::parse; + case 4: return strconv_pcdata_impl::parse; + case 5: return strconv_pcdata_impl::parse; + case 6: return strconv_pcdata_impl::parse; + case 7: return strconv_pcdata_impl::parse; + default: assert(false); return 0; // should not get here + } + } + + typedef char_t* (*strconv_attribute_t)(char_t*, char_t); + + template struct strconv_attribute_impl + { + static char_t* parse_wnorm(char_t* s, char_t end_quote) + { + gap g; + + // trim leading whitespaces + if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + char_t* str = s; + + do ++str; + while (PUGI__IS_CHARTYPE(*str, ct_space)); + + g.push(s, str - s); + } + + while (true) + { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr_ws | ct_space)) ++s; + + if (*s == end_quote) + { + char_t* str = g.flush(s); + + do *str-- = 0; + while (PUGI__IS_CHARTYPE(*str, ct_space)); + + return s + 1; + } + else if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + *s++ = ' '; + + if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + char_t* str = s + 1; + while (PUGI__IS_CHARTYPE(*str, ct_space)) ++str; + + g.push(s, str - s); + } + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + + static char_t* parse_wconv(char_t* s, char_t end_quote) + { + gap g; + + while (true) + { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr_ws)) ++s; + + if (*s == end_quote) + { + *g.flush(s) = 0; + + return s + 1; + } + else if (PUGI__IS_CHARTYPE(*s, ct_space)) + { + if (*s == '\r') + { + *s++ = ' '; + + if (*s == '\n') g.push(s, 1); + } + else *s++ = ' '; + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + + static char_t* parse_eol(char_t* s, char_t end_quote) + { + gap g; + + while (true) + { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr)) ++s; + + if (*s == end_quote) + { + *g.flush(s) = 0; + + return s + 1; + } + else if (*s == '\r') + { + *s++ = '\n'; + + if (*s == '\n') g.push(s, 1); + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + + static char_t* parse_simple(char_t* s, char_t end_quote) + { + gap g; + + while (true) + { + while (!PUGI__IS_CHARTYPE(*s, ct_parse_attr)) ++s; + + if (*s == end_quote) + { + *g.flush(s) = 0; + + return s + 1; + } + else if (opt_escape::value && *s == '&') + { + s = strconv_escape(s, g); + } + else if (!*s) + { + return 0; + } + else ++s; + } + } + }; + + PUGI__FN strconv_attribute_t get_strconv_attribute(unsigned int optmask) + { + PUGI__STATIC_ASSERT(parse_escapes == 0x10 && parse_eol == 0x20 && parse_wconv_attribute == 0x40 && parse_wnorm_attribute == 0x80); + + switch ((optmask >> 4) & 15) // get bitmask for flags (wconv wnorm eol escapes) + { + case 0: return strconv_attribute_impl::parse_simple; + case 1: return strconv_attribute_impl::parse_simple; + case 2: return strconv_attribute_impl::parse_eol; + case 3: return strconv_attribute_impl::parse_eol; + case 4: return strconv_attribute_impl::parse_wconv; + case 5: return strconv_attribute_impl::parse_wconv; + case 6: return strconv_attribute_impl::parse_wconv; + case 7: return strconv_attribute_impl::parse_wconv; + case 8: return strconv_attribute_impl::parse_wnorm; + case 9: return strconv_attribute_impl::parse_wnorm; + case 10: return strconv_attribute_impl::parse_wnorm; + case 11: return strconv_attribute_impl::parse_wnorm; + case 12: return strconv_attribute_impl::parse_wnorm; + case 13: return strconv_attribute_impl::parse_wnorm; + case 14: return strconv_attribute_impl::parse_wnorm; + case 15: return strconv_attribute_impl::parse_wnorm; + default: assert(false); return 0; // should not get here + } + } + + inline xml_parse_result make_parse_result(xml_parse_status status, ptrdiff_t offset = 0) + { + xml_parse_result result; + result.status = status; + result.offset = offset; + + return result; + } + + struct xml_parser + { + xml_allocator alloc; + char_t* error_offset; + xml_parse_status error_status; + + // Parser utilities. + #define PUGI__SKIPWS() { while (PUGI__IS_CHARTYPE(*s, ct_space)) ++s; } + #define PUGI__OPTSET(OPT) ( optmsk & (OPT) ) + #define PUGI__PUSHNODE(TYPE) { cursor = append_node(cursor, alloc, TYPE); if (!cursor) PUGI__THROW_ERROR(status_out_of_memory, s); } + #define PUGI__POPNODE() { cursor = cursor->parent; } + #define PUGI__SCANFOR(X) { while (*s != 0 && !(X)) ++s; } + #define PUGI__SCANWHILE(X) { while ((X)) ++s; } + #define PUGI__ENDSEG() { ch = *s; *s = 0; ++s; } + #define PUGI__THROW_ERROR(err, m) return error_offset = m, error_status = err, static_cast(0) + #define PUGI__CHECK_ERROR(err, m) { if (*s == 0) PUGI__THROW_ERROR(err, m); } + + xml_parser(const xml_allocator& alloc_): alloc(alloc_), error_offset(0), error_status(status_ok) + { + } + + // DOCTYPE consists of nested sections of the following possible types: + // , , "...", '...' + // + // + // First group can not contain nested groups + // Second group can contain nested groups of the same type + // Third group can contain all other groups + char_t* parse_doctype_primitive(char_t* s) + { + if (*s == '"' || *s == '\'') + { + // quoted string + char_t ch = *s++; + PUGI__SCANFOR(*s == ch); + if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + + s++; + } + else if (s[0] == '<' && s[1] == '?') + { + // + s += 2; + PUGI__SCANFOR(s[0] == '?' && s[1] == '>'); // no need for ENDSWITH because ?> can't terminate proper doctype + if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + + s += 2; + } + else if (s[0] == '<' && s[1] == '!' && s[2] == '-' && s[3] == '-') + { + s += 4; + PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && s[2] == '>'); // no need for ENDSWITH because --> can't terminate proper doctype + if (!*s) PUGI__THROW_ERROR(status_bad_doctype, s); + + s += 4; + } + else PUGI__THROW_ERROR(status_bad_doctype, s); + + return s; + } + + char_t* parse_doctype_ignore(char_t* s) + { + assert(s[0] == '<' && s[1] == '!' && s[2] == '['); + s++; + + while (*s) + { + if (s[0] == '<' && s[1] == '!' && s[2] == '[') + { + // nested ignore section + s = parse_doctype_ignore(s); + if (!s) return s; + } + else if (s[0] == ']' && s[1] == ']' && s[2] == '>') + { + // ignore section end + s += 3; + + return s; + } + else s++; + } + + PUGI__THROW_ERROR(status_bad_doctype, s); + } + + char_t* parse_doctype_group(char_t* s, char_t endch, bool toplevel) + { + assert((s[0] == '<' || s[0] == 0) && s[1] == '!'); + s++; + + while (*s) + { + if (s[0] == '<' && s[1] == '!' && s[2] != '-') + { + if (s[2] == '[') + { + // ignore + s = parse_doctype_ignore(s); + if (!s) return s; + } + else + { + // some control group + s = parse_doctype_group(s, endch, false); + if (!s) return s; + + // skip > + assert(*s == '>'); + s++; + } + } + else if (s[0] == '<' || s[0] == '"' || s[0] == '\'') + { + // unknown tag (forbidden), or some primitive group + s = parse_doctype_primitive(s); + if (!s) return s; + } + else if (*s == '>') + { + return s; + } + else s++; + } + + if (!toplevel || endch != '>') PUGI__THROW_ERROR(status_bad_doctype, s); + + return s; + } + + char_t* parse_exclamation(char_t* s, xml_node_struct* cursor, unsigned int optmsk, char_t endch) + { + // parse node contents, starting with exclamation mark + ++s; + + if (*s == '-') // 'value = s; // Save the offset. + } + + if (PUGI__OPTSET(parse_eol) && PUGI__OPTSET(parse_comments)) + { + s = strconv_comment(s, endch); + + if (!s) PUGI__THROW_ERROR(status_bad_comment, cursor->value); + } + else + { + // Scan for terminating '-->'. + PUGI__SCANFOR(s[0] == '-' && s[1] == '-' && ENDSWITH(s[2], '>')); + PUGI__CHECK_ERROR(status_bad_comment, s); + + if (PUGI__OPTSET(parse_comments)) + *s = 0; // Zero-terminate this segment at the first terminating '-'. + + s += (s[2] == '>' ? 3 : 2); // Step over the '\0->'. + } + } + else PUGI__THROW_ERROR(status_bad_comment, s); + } + else if (*s == '[') + { + // 'value = s; // Save the offset. + + if (PUGI__OPTSET(parse_eol)) + { + s = strconv_cdata(s, endch); + + if (!s) PUGI__THROW_ERROR(status_bad_cdata, cursor->value); + } + else + { + // Scan for terminating ']]>'. + PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>')); + PUGI__CHECK_ERROR(status_bad_cdata, s); + + *s++ = 0; // Zero-terminate this segment. + } + } + else // Flagged for discard, but we still have to scan for the terminator. + { + // Scan for terminating ']]>'. + PUGI__SCANFOR(s[0] == ']' && s[1] == ']' && ENDSWITH(s[2], '>')); + PUGI__CHECK_ERROR(status_bad_cdata, s); + + ++s; + } + + s += (s[1] == '>' ? 2 : 1); // Step over the last ']>'. + } + else PUGI__THROW_ERROR(status_bad_cdata, s); + } + else if (s[0] == 'D' && s[1] == 'O' && s[2] == 'C' && s[3] == 'T' && s[4] == 'Y' && s[5] == 'P' && ENDSWITH(s[6], 'E')) + { + s -= 2; + + if (cursor->parent) PUGI__THROW_ERROR(status_bad_doctype, s); + + char_t* mark = s + 9; + + s = parse_doctype_group(s, endch, true); + if (!s) return s; + + assert((*s == 0 && endch == '>') || *s == '>'); + if (*s) *s++ = 0; + + if (PUGI__OPTSET(parse_doctype)) + { + while (PUGI__IS_CHARTYPE(*mark, ct_space)) ++mark; + + PUGI__PUSHNODE(node_doctype); + + cursor->value = mark; + + PUGI__POPNODE(); + } + } + else if (*s == 0 && endch == '-') PUGI__THROW_ERROR(status_bad_comment, s); + else if (*s == 0 && endch == '[') PUGI__THROW_ERROR(status_bad_cdata, s); + else PUGI__THROW_ERROR(status_unrecognized_tag, s); + + return s; + } + + char_t* parse_question(char_t* s, xml_node_struct*& ref_cursor, unsigned int optmsk, char_t endch) + { + // load into registers + xml_node_struct* cursor = ref_cursor; + char_t ch = 0; + + // parse node contents, starting with question mark + ++s; + + // read PI target + char_t* target = s; + + if (!PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_pi, s); + + PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); + PUGI__CHECK_ERROR(status_bad_pi, s); + + // determine node type; stricmp / strcasecmp is not portable + bool declaration = (target[0] | ' ') == 'x' && (target[1] | ' ') == 'm' && (target[2] | ' ') == 'l' && target + 3 == s; + + if (declaration ? PUGI__OPTSET(parse_declaration) : PUGI__OPTSET(parse_pi)) + { + if (declaration) + { + // disallow non top-level declarations + if (cursor->parent) PUGI__THROW_ERROR(status_bad_pi, s); + + PUGI__PUSHNODE(node_declaration); + } + else + { + PUGI__PUSHNODE(node_pi); + } + + cursor->name = target; + + PUGI__ENDSEG(); + + // parse value/attributes + if (ch == '?') + { + // empty node + if (!ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_pi, s); + s += (*s == '>'); + + PUGI__POPNODE(); + } + else if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + PUGI__SKIPWS(); + + // scan for tag end + char_t* value = s; + + PUGI__SCANFOR(s[0] == '?' && ENDSWITH(s[1], '>')); + PUGI__CHECK_ERROR(status_bad_pi, s); + + if (declaration) + { + // replace ending ? with / so that 'element' terminates properly + *s = '/'; + + // we exit from this function with cursor at node_declaration, which is a signal to parse() to go to LOC_ATTRIBUTES + s = value; + } + else + { + // store value and step over > + cursor->value = value; + PUGI__POPNODE(); + + PUGI__ENDSEG(); + + s += (*s == '>'); + } + } + else PUGI__THROW_ERROR(status_bad_pi, s); + } + else + { + // scan for tag end + PUGI__SCANFOR(s[0] == '?' && ENDSWITH(s[1], '>')); + PUGI__CHECK_ERROR(status_bad_pi, s); + + s += (s[1] == '>' ? 2 : 1); + } + + // store from registers + ref_cursor = cursor; + + return s; + } + + char_t* parse_tree(char_t* s, xml_node_struct* root, unsigned int optmsk, char_t endch) + { + strconv_attribute_t strconv_attribute = get_strconv_attribute(optmsk); + strconv_pcdata_t strconv_pcdata = get_strconv_pcdata(optmsk); + + char_t ch = 0; + xml_node_struct* cursor = root; + char_t* mark = s; + + while (*s != 0) + { + if (*s == '<') + { + ++s; + + LOC_TAG: + if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // '<#...' + { + PUGI__PUSHNODE(node_element); // Append a new node to the tree. + + cursor->name = s; + + PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); // Scan for a terminator. + PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. + + if (ch == '>') + { + // end of tag + } + else if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + LOC_ATTRIBUTES: + while (true) + { + PUGI__SKIPWS(); // Eat any whitespace. + + if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) // <... #... + { + xml_attribute_struct* a = append_attribute_ll(cursor, alloc); // Make space for this attribute. + if (!a) PUGI__THROW_ERROR(status_out_of_memory, s); + + a->name = s; // Save the offset. + + PUGI__SCANWHILE(PUGI__IS_CHARTYPE(*s, ct_symbol)); // Scan for a terminator. + PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance + + PUGI__ENDSEG(); // Save char in 'ch', terminate & step over. + PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance + + if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + PUGI__SKIPWS(); // Eat any whitespace. + PUGI__CHECK_ERROR(status_bad_attribute, s); //$ redundant, left for performance + + ch = *s; + ++s; + } + + if (ch == '=') // '<... #=...' + { + PUGI__SKIPWS(); // Eat any whitespace. + + if (*s == '"' || *s == '\'') // '<... #="...' + { + ch = *s; // Save quote char to avoid breaking on "''" -or- '""'. + ++s; // Step over the quote. + a->value = s; // Save the offset. + + s = strconv_attribute(s, ch); + + if (!s) PUGI__THROW_ERROR(status_bad_attribute, a->value); + + // After this line the loop continues from the start; + // Whitespaces, / and > are ok, symbols and EOF are wrong, + // everything else will be detected + if (PUGI__IS_CHARTYPE(*s, ct_start_symbol)) PUGI__THROW_ERROR(status_bad_attribute, s); + } + else PUGI__THROW_ERROR(status_bad_attribute, s); + } + else PUGI__THROW_ERROR(status_bad_attribute, s); + } + else if (*s == '/') + { + ++s; + + if (*s == '>') + { + PUGI__POPNODE(); + s++; + break; + } + else if (*s == 0 && endch == '>') + { + PUGI__POPNODE(); + break; + } + else PUGI__THROW_ERROR(status_bad_start_element, s); + } + else if (*s == '>') + { + ++s; + + break; + } + else if (*s == 0 && endch == '>') + { + break; + } + else PUGI__THROW_ERROR(status_bad_start_element, s); + } + + // !!! + } + else if (ch == '/') // '<#.../' + { + if (!ENDSWITH(*s, '>')) PUGI__THROW_ERROR(status_bad_start_element, s); + + PUGI__POPNODE(); // Pop. + + s += (*s == '>'); + } + else if (ch == 0) + { + // we stepped over null terminator, backtrack & handle closing tag + --s; + + if (endch != '>') PUGI__THROW_ERROR(status_bad_start_element, s); + } + else PUGI__THROW_ERROR(status_bad_start_element, s); + } + else if (*s == '/') + { + ++s; + + char_t* name = cursor->name; + if (!name) PUGI__THROW_ERROR(status_end_element_mismatch, s); + + while (PUGI__IS_CHARTYPE(*s, ct_symbol)) + { + if (*s++ != *name++) PUGI__THROW_ERROR(status_end_element_mismatch, s); + } + + if (*name) + { + if (*s == 0 && name[0] == endch && name[1] == 0) PUGI__THROW_ERROR(status_bad_end_element, s); + else PUGI__THROW_ERROR(status_end_element_mismatch, s); + } + + PUGI__POPNODE(); // Pop. + + PUGI__SKIPWS(); + + if (*s == 0) + { + if (endch != '>') PUGI__THROW_ERROR(status_bad_end_element, s); + } + else + { + if (*s != '>') PUGI__THROW_ERROR(status_bad_end_element, s); + ++s; + } + } + else if (*s == '?') // 'header & xml_memory_page_type_mask) + 1 == node_declaration) goto LOC_ATTRIBUTES; + } + else if (*s == '!') // 'first_child) continue; + } + } + + if (!PUGI__OPTSET(parse_trim_pcdata)) + s = mark; + + if (cursor->parent || PUGI__OPTSET(parse_fragment)) + { + PUGI__PUSHNODE(node_pcdata); // Append a new node on the tree. + cursor->value = s; // Save the offset. + + s = strconv_pcdata(s); + + PUGI__POPNODE(); // Pop since this is a standalone. + + if (!*s) break; + } + else + { + PUGI__SCANFOR(*s == '<'); // '...<' + if (!*s) break; + + ++s; + } + + // We're after '<' + goto LOC_TAG; + } + } + + // check that last tag is closed + if (cursor != root) PUGI__THROW_ERROR(status_end_element_mismatch, s); + + return s; + } + + #ifdef PUGIXML_WCHAR_MODE + static char_t* parse_skip_bom(char_t* s) + { + unsigned int bom = 0xfeff; + return (s[0] == static_cast(bom)) ? s + 1 : s; + } + #else + static char_t* parse_skip_bom(char_t* s) + { + return (s[0] == '\xef' && s[1] == '\xbb' && s[2] == '\xbf') ? s + 3 : s; + } + #endif + + static bool has_element_node_siblings(xml_node_struct* node) + { + while (node) + { + xml_node_type type = static_cast((node->header & impl::xml_memory_page_type_mask) + 1); + if (type == node_element) return true; + + node = node->next_sibling; + } + + return false; + } + + static xml_parse_result parse(char_t* buffer, size_t length, xml_document_struct* xmldoc, xml_node_struct* root, unsigned int optmsk) + { + // allocator object is a part of document object + xml_allocator& alloc = *static_cast(xmldoc); + + // early-out for empty documents + if (length == 0) + return make_parse_result(PUGI__OPTSET(parse_fragment) ? status_ok : status_no_document_element); + + // get last child of the root before parsing + xml_node_struct* last_root_child = root->first_child ? root->first_child->prev_sibling_c : 0; + + // create parser on stack + xml_parser parser(alloc); + + // save last character and make buffer zero-terminated (speeds up parsing) + char_t endch = buffer[length - 1]; + buffer[length - 1] = 0; + + // skip BOM to make sure it does not end up as part of parse output + char_t* buffer_data = parse_skip_bom(buffer); + + // perform actual parsing + parser.parse_tree(buffer_data, root, optmsk, endch); + + // update allocator state + alloc = parser.alloc; + + xml_parse_result result = make_parse_result(parser.error_status, parser.error_offset ? parser.error_offset - buffer : 0); + assert(result.offset >= 0 && static_cast(result.offset) <= length); + + if (result) + { + // since we removed last character, we have to handle the only possible false positive (stray <) + if (endch == '<') + return make_parse_result(status_unrecognized_tag, length - 1); + + // check if there are any element nodes parsed + xml_node_struct* first_root_child_parsed = last_root_child ? last_root_child->next_sibling : root->first_child; + + if (!PUGI__OPTSET(parse_fragment) && !has_element_node_siblings(first_root_child_parsed)) + return make_parse_result(status_no_document_element, length - 1); + } + else + { + // roll back offset if it occurs on a null terminator in the source buffer + if (result.offset > 0 && static_cast(result.offset) == length - 1 && endch == 0) + result.offset--; + } + + return result; + } + }; + + // Output facilities + PUGI__FN xml_encoding get_write_native_encoding() + { + #ifdef PUGIXML_WCHAR_MODE + return get_wchar_encoding(); + #else + return encoding_utf8; + #endif + } + + PUGI__FN xml_encoding get_write_encoding(xml_encoding encoding) + { + // replace wchar encoding with utf implementation + if (encoding == encoding_wchar) return get_wchar_encoding(); + + // replace utf16 encoding with utf16 with specific endianness + if (encoding == encoding_utf16) return is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + // replace utf32 encoding with utf32 with specific endianness + if (encoding == encoding_utf32) return is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + // only do autodetection if no explicit encoding is requested + if (encoding != encoding_auto) return encoding; + + // assume utf8 encoding + return encoding_utf8; + } + +#ifdef PUGIXML_WCHAR_MODE + PUGI__FN size_t get_valid_length(const char_t* data, size_t length) + { + assert(length > 0); + + // discard last character if it's the lead of a surrogate pair + return (sizeof(wchar_t) == 2 && static_cast(static_cast(data[length - 1]) - 0xD800) < 0x400) ? length - 1 : length; + } + + PUGI__FN size_t convert_buffer_output(char_t* r_char, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) + { + // only endian-swapping is required + if (need_endian_swap_utf(encoding, get_wchar_encoding())) + { + convert_wchar_endian_swap(r_char, data, length); + + return length * sizeof(char_t); + } + + // convert to utf8 + if (encoding == encoding_utf8) + { + uint8_t* dest = r_u8; + uint8_t* end = utf_decoder::decode_wchar_block(data, length, dest); + + return static_cast(end - dest); + } + + // convert to utf16 + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + uint16_t* dest = r_u16; + + // convert to native utf16 + uint16_t* end = utf_decoder::decode_wchar_block(data, length, dest); + + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); + + return static_cast(end - dest) * sizeof(uint16_t); + } + + // convert to utf32 + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + uint32_t* dest = r_u32; + + // convert to native utf32 + uint32_t* end = utf_decoder::decode_wchar_block(data, length, dest); + + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); + + return static_cast(end - dest) * sizeof(uint32_t); + } + + // convert to latin1 + if (encoding == encoding_latin1) + { + uint8_t* dest = r_u8; + uint8_t* end = utf_decoder::decode_wchar_block(data, length, dest); + + return static_cast(end - dest); + } + + assert(!"Invalid encoding"); + return 0; + } +#else + PUGI__FN size_t get_valid_length(const char_t* data, size_t length) + { + assert(length > 4); + + for (size_t i = 1; i <= 4; ++i) + { + uint8_t ch = static_cast(data[length - i]); + + // either a standalone character or a leading one + if ((ch & 0xc0) != 0x80) return length - i; + } + + // there are four non-leading characters at the end, sequence tail is broken so might as well process the whole chunk + return length; + } + + PUGI__FN size_t convert_buffer_output(char_t* /* r_char */, uint8_t* r_u8, uint16_t* r_u16, uint32_t* r_u32, const char_t* data, size_t length, xml_encoding encoding) + { + if (encoding == encoding_utf16_be || encoding == encoding_utf16_le) + { + uint16_t* dest = r_u16; + + // convert to native utf16 + uint16_t* end = utf_decoder::decode_utf8_block(reinterpret_cast(data), length, dest); + + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf16_le : encoding_utf16_be; + + if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); + + return static_cast(end - dest) * sizeof(uint16_t); + } + + if (encoding == encoding_utf32_be || encoding == encoding_utf32_le) + { + uint32_t* dest = r_u32; + + // convert to native utf32 + uint32_t* end = utf_decoder::decode_utf8_block(reinterpret_cast(data), length, dest); + + // swap if necessary + xml_encoding native_encoding = is_little_endian() ? encoding_utf32_le : encoding_utf32_be; + + if (native_encoding != encoding) convert_utf_endian_swap(dest, dest, static_cast(end - dest)); + + return static_cast(end - dest) * sizeof(uint32_t); + } + + if (encoding == encoding_latin1) + { + uint8_t* dest = r_u8; + uint8_t* end = utf_decoder::decode_utf8_block(reinterpret_cast(data), length, dest); + + return static_cast(end - dest); + } + + assert(!"Invalid encoding"); + return 0; + } +#endif + + class xml_buffered_writer + { + xml_buffered_writer(const xml_buffered_writer&); + xml_buffered_writer& operator=(const xml_buffered_writer&); + + public: + xml_buffered_writer(xml_writer& writer_, xml_encoding user_encoding): writer(writer_), bufsize(0), encoding(get_write_encoding(user_encoding)) + { + PUGI__STATIC_ASSERT(bufcapacity >= 8); + } + + ~xml_buffered_writer() + { + flush(); + } + + void flush() + { + flush(buffer, bufsize); + bufsize = 0; + } + + void flush(const char_t* data, size_t size) + { + if (size == 0) return; + + // fast path, just write data + if (encoding == get_write_native_encoding()) + writer.write(data, size * sizeof(char_t)); + else + { + // convert chunk + size_t result = convert_buffer_output(scratch.data_char, scratch.data_u8, scratch.data_u16, scratch.data_u32, data, size, encoding); + assert(result <= sizeof(scratch)); + + // write data + writer.write(scratch.data_u8, result); + } + } + + void write(const char_t* data, size_t length) + { + if (bufsize + length > bufcapacity) + { + // flush the remaining buffer contents + flush(); + + // handle large chunks + if (length > bufcapacity) + { + if (encoding == get_write_native_encoding()) + { + // fast path, can just write data chunk + writer.write(data, length * sizeof(char_t)); + return; + } + + // need to convert in suitable chunks + while (length > bufcapacity) + { + // get chunk size by selecting such number of characters that are guaranteed to fit into scratch buffer + // and form a complete codepoint sequence (i.e. discard start of last codepoint if necessary) + size_t chunk_size = get_valid_length(data, bufcapacity); + + // convert chunk and write + flush(data, chunk_size); + + // iterate + data += chunk_size; + length -= chunk_size; + } + + // small tail is copied below + bufsize = 0; + } + } + + memcpy(buffer + bufsize, data, length * sizeof(char_t)); + bufsize += length; + } + + void write(const char_t* data) + { + write(data, strlength(data)); + } + + void write(char_t d0) + { + if (bufsize + 1 > bufcapacity) flush(); + + buffer[bufsize + 0] = d0; + bufsize += 1; + } + + void write(char_t d0, char_t d1) + { + if (bufsize + 2 > bufcapacity) flush(); + + buffer[bufsize + 0] = d0; + buffer[bufsize + 1] = d1; + bufsize += 2; + } + + void write(char_t d0, char_t d1, char_t d2) + { + if (bufsize + 3 > bufcapacity) flush(); + + buffer[bufsize + 0] = d0; + buffer[bufsize + 1] = d1; + buffer[bufsize + 2] = d2; + bufsize += 3; + } + + void write(char_t d0, char_t d1, char_t d2, char_t d3) + { + if (bufsize + 4 > bufcapacity) flush(); + + buffer[bufsize + 0] = d0; + buffer[bufsize + 1] = d1; + buffer[bufsize + 2] = d2; + buffer[bufsize + 3] = d3; + bufsize += 4; + } + + void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4) + { + if (bufsize + 5 > bufcapacity) flush(); + + buffer[bufsize + 0] = d0; + buffer[bufsize + 1] = d1; + buffer[bufsize + 2] = d2; + buffer[bufsize + 3] = d3; + buffer[bufsize + 4] = d4; + bufsize += 5; + } + + void write(char_t d0, char_t d1, char_t d2, char_t d3, char_t d4, char_t d5) + { + if (bufsize + 6 > bufcapacity) flush(); + + buffer[bufsize + 0] = d0; + buffer[bufsize + 1] = d1; + buffer[bufsize + 2] = d2; + buffer[bufsize + 3] = d3; + buffer[bufsize + 4] = d4; + buffer[bufsize + 5] = d5; + bufsize += 6; + } + + // utf8 maximum expansion: x4 (-> utf32) + // utf16 maximum expansion: x2 (-> utf32) + // utf32 maximum expansion: x1 + enum + { + bufcapacitybytes = + #ifdef PUGIXML_MEMORY_OUTPUT_STACK + PUGIXML_MEMORY_OUTPUT_STACK + #else + 10240 + #endif + , + bufcapacity = bufcapacitybytes / (sizeof(char_t) + 4) + }; + + char_t buffer[bufcapacity]; + + union + { + uint8_t data_u8[4 * bufcapacity]; + uint16_t data_u16[2 * bufcapacity]; + uint32_t data_u32[bufcapacity]; + char_t data_char[bufcapacity]; + } scratch; + + xml_writer& writer; + size_t bufsize; + xml_encoding encoding; + }; + + PUGI__FN void text_output_escaped(xml_buffered_writer& writer, const char_t* s, chartypex_t type) + { + while (*s) + { + const char_t* prev = s; + + // While *s is a usual symbol + while (!PUGI__IS_CHARTYPEX(*s, type)) ++s; + + writer.write(prev, static_cast(s - prev)); + + switch (*s) + { + case 0: break; + case '&': + writer.write('&', 'a', 'm', 'p', ';'); + ++s; + break; + case '<': + writer.write('&', 'l', 't', ';'); + ++s; + break; + case '>': + writer.write('&', 'g', 't', ';'); + ++s; + break; + case '"': + writer.write('&', 'q', 'u', 'o', 't', ';'); + ++s; + break; + default: // s is not a usual symbol + { + unsigned int ch = static_cast(*s++); + assert(ch < 32); + + writer.write('&', '#', static_cast((ch / 10) + '0'), static_cast((ch % 10) + '0'), ';'); + } + } + } + } + + PUGI__FN void text_output(xml_buffered_writer& writer, const char_t* s, chartypex_t type, unsigned int flags) + { + if (flags & format_no_escapes) + writer.write(s); + else + text_output_escaped(writer, s, type); + } + + PUGI__FN void text_output_cdata(xml_buffered_writer& writer, const char_t* s) + { + do + { + writer.write('<', '!', '[', 'C', 'D'); + writer.write('A', 'T', 'A', '['); + + const char_t* prev = s; + + // look for ]]> sequence - we can't output it as is since it terminates CDATA + while (*s && !(s[0] == ']' && s[1] == ']' && s[2] == '>')) ++s; + + // skip ]] if we stopped at ]]>, > will go to the next CDATA section + if (*s) s += 2; + + writer.write(prev, static_cast(s - prev)); + + writer.write(']', ']', '>'); + } + while (*s); + } + + PUGI__FN void node_output_attributes(xml_buffered_writer& writer, const xml_node& node, unsigned int flags) + { + const char_t* default_name = PUGIXML_TEXT(":anonymous"); + + for (xml_attribute a = node.first_attribute(); a; a = a.next_attribute()) + { + writer.write(' '); + writer.write(a.name()[0] ? a.name() : default_name); + writer.write('=', '"'); + + text_output(writer, a.value(), ctx_special_attr, flags); + + writer.write('"'); + } + } + + PUGI__FN void node_output(xml_buffered_writer& writer, const xml_node& node, const char_t* indent, unsigned int flags, unsigned int depth) + { + const char_t* default_name = PUGIXML_TEXT(":anonymous"); + + if ((flags & format_indent) != 0 && (flags & format_raw) == 0) + for (unsigned int i = 0; i < depth; ++i) writer.write(indent); + + switch (node.type()) + { + case node_document: + { + for (xml_node n = node.first_child(); n; n = n.next_sibling()) + node_output(writer, n, indent, flags, depth); + break; + } + + case node_element: + { + const char_t* name = node.name()[0] ? node.name() : default_name; + + writer.write('<'); + writer.write(name); + + node_output_attributes(writer, node, flags); + + if (flags & format_raw) + { + if (!node.first_child()) + writer.write(' ', '/', '>'); + else + { + writer.write('>'); + + for (xml_node n = node.first_child(); n; n = n.next_sibling()) + node_output(writer, n, indent, flags, depth + 1); + + writer.write('<', '/'); + writer.write(name); + writer.write('>'); + } + } + else if (!node.first_child()) + writer.write(' ', '/', '>', '\n'); + else if (node.first_child() == node.last_child() && (node.first_child().type() == node_pcdata || node.first_child().type() == node_cdata)) + { + writer.write('>'); + + if (node.first_child().type() == node_pcdata) + text_output(writer, node.first_child().value(), ctx_special_pcdata, flags); + else + text_output_cdata(writer, node.first_child().value()); + + writer.write('<', '/'); + writer.write(name); + writer.write('>', '\n'); + } + else + { + writer.write('>', '\n'); + + for (xml_node n = node.first_child(); n; n = n.next_sibling()) + node_output(writer, n, indent, flags, depth + 1); + + if ((flags & format_indent) != 0 && (flags & format_raw) == 0) + for (unsigned int i = 0; i < depth; ++i) writer.write(indent); + + writer.write('<', '/'); + writer.write(name); + writer.write('>', '\n'); + } + + break; + } + + case node_pcdata: + text_output(writer, node.value(), ctx_special_pcdata, flags); + if ((flags & format_raw) == 0) writer.write('\n'); + break; + + case node_cdata: + text_output_cdata(writer, node.value()); + if ((flags & format_raw) == 0) writer.write('\n'); + break; + + case node_comment: + writer.write('<', '!', '-', '-'); + writer.write(node.value()); + writer.write('-', '-', '>'); + if ((flags & format_raw) == 0) writer.write('\n'); + break; + + case node_pi: + case node_declaration: + writer.write('<', '?'); + writer.write(node.name()[0] ? node.name() : default_name); + + if (node.type() == node_declaration) + { + node_output_attributes(writer, node, flags); + } + else if (node.value()[0]) + { + writer.write(' '); + writer.write(node.value()); + } + + writer.write('?', '>'); + if ((flags & format_raw) == 0) writer.write('\n'); + break; + + case node_doctype: + writer.write('<', '!', 'D', 'O', 'C'); + writer.write('T', 'Y', 'P', 'E'); + + if (node.value()[0]) + { + writer.write(' '); + writer.write(node.value()); + } + + writer.write('>'); + if ((flags & format_raw) == 0) writer.write('\n'); + break; + + default: + assert(!"Invalid node type"); + } + } + + inline bool has_declaration(const xml_node& node) + { + for (xml_node child = node.first_child(); child; child = child.next_sibling()) + { + xml_node_type type = child.type(); + + if (type == node_declaration) return true; + if (type == node_element) return false; + } + + return false; + } + + inline bool allow_insert_child(xml_node_type parent, xml_node_type child) + { + if (parent != node_document && parent != node_element) return false; + if (child == node_document || child == node_null) return false; + if (parent != node_document && (child == node_declaration || child == node_doctype)) return false; + + return true; + } + + PUGI__FN void recursive_copy_skip(xml_node& dest, const xml_node& source, const xml_node& skip) + { + assert(dest.type() == source.type()); + + switch (source.type()) + { + case node_element: + { + dest.set_name(source.name()); + + for (xml_attribute a = source.first_attribute(); a; a = a.next_attribute()) + dest.append_attribute(a.name()).set_value(a.value()); + + for (xml_node c = source.first_child(); c; c = c.next_sibling()) + { + if (c == skip) continue; + + xml_node cc = dest.append_child(c.type()); + assert(cc); + + recursive_copy_skip(cc, c, skip); + } + + break; + } + + case node_pcdata: + case node_cdata: + case node_comment: + case node_doctype: + dest.set_value(source.value()); + break; + + case node_pi: + dest.set_name(source.name()); + dest.set_value(source.value()); + break; + + case node_declaration: + { + dest.set_name(source.name()); + + for (xml_attribute a = source.first_attribute(); a; a = a.next_attribute()) + dest.append_attribute(a.name()).set_value(a.value()); + + break; + } + + default: + assert(!"Invalid node type"); + } + } + + inline bool is_text_node(xml_node_struct* node) + { + xml_node_type type = static_cast((node->header & impl::xml_memory_page_type_mask) + 1); + + return type == node_pcdata || type == node_cdata; + } + + // get value with conversion functions + PUGI__FN int get_integer_base(const char_t* value) + { + const char_t* s = value; + + while (PUGI__IS_CHARTYPE(*s, ct_space)) + s++; + + if (*s == '-') + s++; + + return (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) ? 16 : 10; + } + + PUGI__FN int get_value_int(const char_t* value, int def) + { + if (!value) return def; + + int base = get_integer_base(value); + + #ifdef PUGIXML_WCHAR_MODE + return static_cast(wcstol(value, 0, base)); + #else + return static_cast(strtol(value, 0, base)); + #endif + } + + PUGI__FN unsigned int get_value_uint(const char_t* value, unsigned int def) + { + if (!value) return def; + + int base = get_integer_base(value); + + #ifdef PUGIXML_WCHAR_MODE + return static_cast(wcstoul(value, 0, base)); + #else + return static_cast(strtoul(value, 0, base)); + #endif + } + + PUGI__FN double get_value_double(const char_t* value, double def) + { + if (!value) return def; + + #ifdef PUGIXML_WCHAR_MODE + return wcstod(value, 0); + #else + return strtod(value, 0); + #endif + } + + PUGI__FN float get_value_float(const char_t* value, float def) + { + if (!value) return def; + + #ifdef PUGIXML_WCHAR_MODE + return static_cast(wcstod(value, 0)); + #else + return static_cast(strtod(value, 0)); + #endif + } + + PUGI__FN bool get_value_bool(const char_t* value, bool def) + { + if (!value) return def; + + // only look at first char + char_t first = *value; + + // 1*, t* (true), T* (True), y* (yes), Y* (YES) + return (first == '1' || first == 't' || first == 'T' || first == 'y' || first == 'Y'); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN long long get_value_llong(const char_t* value, long long def) + { + if (!value) return def; + + int base = get_integer_base(value); + + #ifdef PUGIXML_WCHAR_MODE + #ifdef PUGI__MSVC_CRT_VERSION + return _wcstoi64(value, 0, base); + #else + return wcstoll(value, 0, base); + #endif + #else + #ifdef PUGI__MSVC_CRT_VERSION + return _strtoi64(value, 0, base); + #else + return strtoll(value, 0, base); + #endif + #endif + } + + PUGI__FN unsigned long long get_value_ullong(const char_t* value, unsigned long long def) + { + if (!value) return def; + + int base = get_integer_base(value); + + #ifdef PUGIXML_WCHAR_MODE + #ifdef PUGI__MSVC_CRT_VERSION + return _wcstoui64(value, 0, base); + #else + return wcstoull(value, 0, base); + #endif + #else + #ifdef PUGI__MSVC_CRT_VERSION + return _strtoui64(value, 0, base); + #else + return strtoull(value, 0, base); + #endif + #endif + } +#endif + + // set value with conversion functions + PUGI__FN bool set_value_buffer(char_t*& dest, uintptr_t& header, uintptr_t header_mask, char (&buf)[128]) + { + #ifdef PUGIXML_WCHAR_MODE + char_t wbuf[128]; + impl::widen_ascii(wbuf, buf); + + return strcpy_insitu(dest, header, header_mask, wbuf); + #else + return strcpy_insitu(dest, header, header_mask, buf); + #endif + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, int value) + { + char buf[128]; + sprintf(buf, "%d", value); + + return set_value_buffer(dest, header, header_mask, buf); + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, unsigned int value) + { + char buf[128]; + sprintf(buf, "%u", value); + + return set_value_buffer(dest, header, header_mask, buf); + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, double value) + { + char buf[128]; + sprintf(buf, "%g", value); + + return set_value_buffer(dest, header, header_mask, buf); + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, bool value) + { + return strcpy_insitu(dest, header, header_mask, value ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false")); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, long long value) + { + char buf[128]; + sprintf(buf, "%lld", value); + + return set_value_buffer(dest, header, header_mask, buf); + } + + PUGI__FN bool set_value_convert(char_t*& dest, uintptr_t& header, uintptr_t header_mask, unsigned long long value) + { + char buf[128]; + sprintf(buf, "%llu", value); + + return set_value_buffer(dest, header, header_mask, buf); + } +#endif + + // we need to get length of entire file to load it in memory; the only (relatively) sane way to do it is via seek/tell trick + PUGI__FN xml_parse_status get_file_size(FILE* file, size_t& out_result) + { + #if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 && !defined(_WIN32_WCE) + // there are 64-bit versions of fseek/ftell, let's use them + typedef __int64 length_type; + + _fseeki64(file, 0, SEEK_END); + length_type length = _ftelli64(file); + _fseeki64(file, 0, SEEK_SET); + #elif defined(__MINGW32__) && !defined(__NO_MINGW_LFS) && !defined(__STRICT_ANSI__) + // there are 64-bit versions of fseek/ftell, let's use them + typedef off64_t length_type; + + fseeko64(file, 0, SEEK_END); + length_type length = ftello64(file); + fseeko64(file, 0, SEEK_SET); + #else + // if this is a 32-bit OS, long is enough; if this is a unix system, long is 64-bit, which is enough; otherwise we can't do anything anyway. + typedef long length_type; + + fseek(file, 0, SEEK_END); + length_type length = ftell(file); + fseek(file, 0, SEEK_SET); + #endif + + // check for I/O errors + if (length < 0) return status_io_error; + + // check for overflow + size_t result = static_cast(length); + + if (static_cast(result) != length) return status_out_of_memory; + + // finalize + out_result = result; + + return status_ok; + } + + PUGI__FN size_t zero_terminate_buffer(void* buffer, size_t size, xml_encoding encoding) + { + // We only need to zero-terminate if encoding conversion does not do it for us + #ifdef PUGIXML_WCHAR_MODE + xml_encoding wchar_encoding = get_wchar_encoding(); + + if (encoding == wchar_encoding || need_endian_swap_utf(encoding, wchar_encoding)) + { + size_t length = size / sizeof(char_t); + + static_cast(buffer)[length] = 0; + return (length + 1) * sizeof(char_t); + } + #else + if (encoding == encoding_utf8) + { + static_cast(buffer)[size] = 0; + return size + 1; + } + #endif + + return size; + } + + PUGI__FN xml_parse_result load_file_impl(xml_document& doc, FILE* file, unsigned int options, xml_encoding encoding) + { + if (!file) return make_parse_result(status_file_not_found); + + // get file size (can result in I/O errors) + size_t size = 0; + xml_parse_status size_status = get_file_size(file, size); + + if (size_status != status_ok) + { + fclose(file); + return make_parse_result(size_status); + } + + size_t max_suffix_size = sizeof(char_t); + + // allocate buffer for the whole file + char* contents = static_cast(xml_memory::allocate(size + max_suffix_size)); + + if (!contents) + { + fclose(file); + return make_parse_result(status_out_of_memory); + } + + // read file in memory + size_t read_size = fread(contents, 1, size, file); + fclose(file); + + if (read_size != size) + { + xml_memory::deallocate(contents); + return make_parse_result(status_io_error); + } + + xml_encoding real_encoding = get_buffer_encoding(encoding, contents, size); + + return doc.load_buffer_inplace_own(contents, zero_terminate_buffer(contents, size, real_encoding), options, real_encoding); + } + +#ifndef PUGIXML_NO_STL + template struct xml_stream_chunk + { + static xml_stream_chunk* create() + { + void* memory = xml_memory::allocate(sizeof(xml_stream_chunk)); + + return new (memory) xml_stream_chunk(); + } + + static void destroy(void* ptr) + { + xml_stream_chunk* chunk = static_cast(ptr); + + // free chunk chain + while (chunk) + { + xml_stream_chunk* next = chunk->next; + xml_memory::deallocate(chunk); + chunk = next; + } + } + + xml_stream_chunk(): next(0), size(0) + { + } + + xml_stream_chunk* next; + size_t size; + + T data[xml_memory_page_size / sizeof(T)]; + }; + + template PUGI__FN xml_parse_status load_stream_data_noseek(std::basic_istream& stream, void** out_buffer, size_t* out_size) + { + buffer_holder chunks(0, xml_stream_chunk::destroy); + + // read file to a chunk list + size_t total = 0; + xml_stream_chunk* last = 0; + + while (!stream.eof()) + { + // allocate new chunk + xml_stream_chunk* chunk = xml_stream_chunk::create(); + if (!chunk) return status_out_of_memory; + + // append chunk to list + if (last) last = last->next = chunk; + else chunks.data = last = chunk; + + // read data to chunk + stream.read(chunk->data, static_cast(sizeof(chunk->data) / sizeof(T))); + chunk->size = static_cast(stream.gcount()) * sizeof(T); + + // read may set failbit | eofbit in case gcount() is less than read length, so check for other I/O errors + if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; + + // guard against huge files (chunk size is small enough to make this overflow check work) + if (total + chunk->size < total) return status_out_of_memory; + total += chunk->size; + } + + size_t max_suffix_size = sizeof(char_t); + + // copy chunk list to a contiguous buffer + char* buffer = static_cast(xml_memory::allocate(total + max_suffix_size)); + if (!buffer) return status_out_of_memory; + + char* write = buffer; + + for (xml_stream_chunk* chunk = static_cast*>(chunks.data); chunk; chunk = chunk->next) + { + assert(write + chunk->size <= buffer + total); + memcpy(write, chunk->data, chunk->size); + write += chunk->size; + } + + assert(write == buffer + total); + + // return buffer + *out_buffer = buffer; + *out_size = total; + + return status_ok; + } + + template PUGI__FN xml_parse_status load_stream_data_seek(std::basic_istream& stream, void** out_buffer, size_t* out_size) + { + // get length of remaining data in stream + typename std::basic_istream::pos_type pos = stream.tellg(); + stream.seekg(0, std::ios::end); + std::streamoff length = stream.tellg() - pos; + stream.seekg(pos); + + if (stream.fail() || pos < 0) return status_io_error; + + // guard against huge files + size_t read_length = static_cast(length); + + if (static_cast(read_length) != length || length < 0) return status_out_of_memory; + + size_t max_suffix_size = sizeof(char_t); + + // read stream data into memory (guard against stream exceptions with buffer holder) + buffer_holder buffer(xml_memory::allocate(read_length * sizeof(T) + max_suffix_size), xml_memory::deallocate); + if (!buffer.data) return status_out_of_memory; + + stream.read(static_cast(buffer.data), static_cast(read_length)); + + // read may set failbit | eofbit in case gcount() is less than read_length (i.e. line ending conversion), so check for other I/O errors + if (stream.bad() || (!stream.eof() && stream.fail())) return status_io_error; + + // return buffer + size_t actual_length = static_cast(stream.gcount()); + assert(actual_length <= read_length); + + *out_buffer = buffer.release(); + *out_size = actual_length * sizeof(T); + + return status_ok; + } + + template PUGI__FN xml_parse_result load_stream_impl(xml_document& doc, std::basic_istream& stream, unsigned int options, xml_encoding encoding) + { + void* buffer = 0; + size_t size = 0; + xml_parse_status status = status_ok; + + // if stream has an error bit set, bail out (otherwise tellg() can fail and we'll clear error bits) + if (stream.fail()) return make_parse_result(status_io_error); + + // load stream to memory (using seek-based implementation if possible, since it's faster and takes less memory) + if (stream.tellg() < 0) + { + stream.clear(); // clear error flags that could be set by a failing tellg + status = load_stream_data_noseek(stream, &buffer, &size); + } + else + status = load_stream_data_seek(stream, &buffer, &size); + + if (status != status_ok) return make_parse_result(status); + + xml_encoding real_encoding = get_buffer_encoding(encoding, buffer, size); + + return doc.load_buffer_inplace_own(buffer, zero_terminate_buffer(buffer, size, real_encoding), options, real_encoding); + } +#endif + +#if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) || (defined(__MINGW32__) && !defined(__STRICT_ANSI__)) + PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) + { + return _wfopen(path, mode); + } +#else + PUGI__FN char* convert_path_heap(const wchar_t* str) + { + assert(str); + + // first pass: get length in utf8 characters + size_t length = strlength_wide(str); + size_t size = as_utf8_begin(str, length); + + // allocate resulting string + char* result = static_cast(xml_memory::allocate(size + 1)); + if (!result) return 0; + + // second pass: convert to utf8 + as_utf8_end(result, size, str, length); + + return result; + } + + PUGI__FN FILE* open_file_wide(const wchar_t* path, const wchar_t* mode) + { + // there is no standard function to open wide paths, so our best bet is to try utf8 path + char* path_utf8 = convert_path_heap(path); + if (!path_utf8) return 0; + + // convert mode to ASCII (we mirror _wfopen interface) + char mode_ascii[4] = {0}; + for (size_t i = 0; mode[i]; ++i) mode_ascii[i] = static_cast(mode[i]); + + // try to open the utf8 path + FILE* result = fopen(path_utf8, mode_ascii); + + // free dummy buffer + xml_memory::deallocate(path_utf8); + + return result; + } +#endif + + PUGI__FN bool save_file_impl(const xml_document& doc, FILE* file, const char_t* indent, unsigned int flags, xml_encoding encoding) + { + if (!file) return false; + + xml_writer_file writer(file); + doc.save(writer, indent, flags, encoding); + + int result = ferror(file); + + fclose(file); + + return result == 0; + } + + PUGI__FN xml_parse_result load_buffer_impl(xml_document_struct* doc, xml_node_struct* root, void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own, char_t** out_buffer) + { + // check input buffer + assert(contents || size == 0); + + // get actual encoding + xml_encoding buffer_encoding = impl::get_buffer_encoding(encoding, contents, size); + + // get private buffer + char_t* buffer = 0; + size_t length = 0; + + if (!impl::convert_buffer(buffer, length, buffer_encoding, contents, size, is_mutable)) return impl::make_parse_result(status_out_of_memory); + + // delete original buffer if we performed a conversion + if (own && buffer != contents && contents) impl::xml_memory::deallocate(contents); + + // store buffer for offset_debug + doc->buffer = buffer; + + // parse + xml_parse_result res = impl::xml_parser::parse(buffer, length, doc, root, options); + + // remember encoding + res.encoding = buffer_encoding; + + // grab onto buffer if it's our buffer, user is responsible for deallocating contents himself + if (own || buffer != contents) *out_buffer = buffer; + + return res; + } +PUGI__NS_END + +namespace pugi +{ + PUGI__FN xml_writer_file::xml_writer_file(void* file_): file(file_) + { + } + + PUGI__FN void xml_writer_file::write(const void* data, size_t size) + { + size_t result = fwrite(data, 1, size, static_cast(file)); + (void)!result; // unfortunately we can't do proper error handling here + } + +#ifndef PUGIXML_NO_STL + PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(&stream), wide_stream(0) + { + } + + PUGI__FN xml_writer_stream::xml_writer_stream(std::basic_ostream >& stream): narrow_stream(0), wide_stream(&stream) + { + } + + PUGI__FN void xml_writer_stream::write(const void* data, size_t size) + { + if (narrow_stream) + { + assert(!wide_stream); + narrow_stream->write(reinterpret_cast(data), static_cast(size)); + } + else + { + assert(wide_stream); + assert(size % sizeof(wchar_t) == 0); + + wide_stream->write(reinterpret_cast(data), static_cast(size / sizeof(wchar_t))); + } + } +#endif + + PUGI__FN xml_tree_walker::xml_tree_walker(): _depth(0) + { + } + + PUGI__FN xml_tree_walker::~xml_tree_walker() + { + } + + PUGI__FN int xml_tree_walker::depth() const + { + return _depth; + } + + PUGI__FN bool xml_tree_walker::begin(xml_node&) + { + return true; + } + + PUGI__FN bool xml_tree_walker::end(xml_node&) + { + return true; + } + + PUGI__FN xml_attribute::xml_attribute(): _attr(0) + { + } + + PUGI__FN xml_attribute::xml_attribute(xml_attribute_struct* attr): _attr(attr) + { + } + + PUGI__FN static void unspecified_bool_xml_attribute(xml_attribute***) + { + } + + PUGI__FN xml_attribute::operator xml_attribute::unspecified_bool_type() const + { + return _attr ? unspecified_bool_xml_attribute : 0; + } + + PUGI__FN bool xml_attribute::operator!() const + { + return !_attr; + } + + PUGI__FN bool xml_attribute::operator==(const xml_attribute& r) const + { + return (_attr == r._attr); + } + + PUGI__FN bool xml_attribute::operator!=(const xml_attribute& r) const + { + return (_attr != r._attr); + } + + PUGI__FN bool xml_attribute::operator<(const xml_attribute& r) const + { + return (_attr < r._attr); + } + + PUGI__FN bool xml_attribute::operator>(const xml_attribute& r) const + { + return (_attr > r._attr); + } + + PUGI__FN bool xml_attribute::operator<=(const xml_attribute& r) const + { + return (_attr <= r._attr); + } + + PUGI__FN bool xml_attribute::operator>=(const xml_attribute& r) const + { + return (_attr >= r._attr); + } + + PUGI__FN xml_attribute xml_attribute::next_attribute() const + { + return _attr ? xml_attribute(_attr->next_attribute) : xml_attribute(); + } + + PUGI__FN xml_attribute xml_attribute::previous_attribute() const + { + return _attr && _attr->prev_attribute_c->next_attribute ? xml_attribute(_attr->prev_attribute_c) : xml_attribute(); + } + + PUGI__FN const char_t* xml_attribute::as_string(const char_t* def) const + { + return (_attr && _attr->value) ? _attr->value : def; + } + + PUGI__FN int xml_attribute::as_int(int def) const + { + return impl::get_value_int(_attr ? _attr->value : 0, def); + } + + PUGI__FN unsigned int xml_attribute::as_uint(unsigned int def) const + { + return impl::get_value_uint(_attr ? _attr->value : 0, def); + } + + PUGI__FN double xml_attribute::as_double(double def) const + { + return impl::get_value_double(_attr ? _attr->value : 0, def); + } + + PUGI__FN float xml_attribute::as_float(float def) const + { + return impl::get_value_float(_attr ? _attr->value : 0, def); + } + + PUGI__FN bool xml_attribute::as_bool(bool def) const + { + return impl::get_value_bool(_attr ? _attr->value : 0, def); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN long long xml_attribute::as_llong(long long def) const + { + return impl::get_value_llong(_attr ? _attr->value : 0, def); + } + + PUGI__FN unsigned long long xml_attribute::as_ullong(unsigned long long def) const + { + return impl::get_value_ullong(_attr ? _attr->value : 0, def); + } +#endif + + PUGI__FN bool xml_attribute::empty() const + { + return !_attr; + } + + PUGI__FN const char_t* xml_attribute::name() const + { + return (_attr && _attr->name) ? _attr->name : PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* xml_attribute::value() const + { + return (_attr && _attr->value) ? _attr->value : PUGIXML_TEXT(""); + } + + PUGI__FN size_t xml_attribute::hash_value() const + { + return static_cast(reinterpret_cast(_attr) / sizeof(xml_attribute_struct)); + } + + PUGI__FN xml_attribute_struct* xml_attribute::internal_object() const + { + return _attr; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(const char_t* rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(int rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(unsigned int rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(double rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(bool rhs) + { + set_value(rhs); + return *this; + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN xml_attribute& xml_attribute::operator=(long long rhs) + { + set_value(rhs); + return *this; + } + + PUGI__FN xml_attribute& xml_attribute::operator=(unsigned long long rhs) + { + set_value(rhs); + return *this; + } +#endif + + PUGI__FN bool xml_attribute::set_name(const char_t* rhs) + { + if (!_attr) return false; + + return impl::strcpy_insitu(_attr->name, _attr->header, impl::xml_memory_page_name_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(const char_t* rhs) + { + if (!_attr) return false; + + return impl::strcpy_insitu(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(int rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(unsigned int rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(double rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(bool rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN bool xml_attribute::set_value(long long rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } + + PUGI__FN bool xml_attribute::set_value(unsigned long long rhs) + { + if (!_attr) return false; + + return impl::set_value_convert(_attr->value, _attr->header, impl::xml_memory_page_value_allocated_mask, rhs); + } +#endif + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xml_attribute& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xml_attribute& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN xml_node::xml_node(): _root(0) + { + } + + PUGI__FN xml_node::xml_node(xml_node_struct* p): _root(p) + { + } + + PUGI__FN static void unspecified_bool_xml_node(xml_node***) + { + } + + PUGI__FN xml_node::operator xml_node::unspecified_bool_type() const + { + return _root ? unspecified_bool_xml_node : 0; + } + + PUGI__FN bool xml_node::operator!() const + { + return !_root; + } + + PUGI__FN xml_node::iterator xml_node::begin() const + { + return iterator(_root ? _root->first_child : 0, _root); + } + + PUGI__FN xml_node::iterator xml_node::end() const + { + return iterator(0, _root); + } + + PUGI__FN xml_node::attribute_iterator xml_node::attributes_begin() const + { + return attribute_iterator(_root ? _root->first_attribute : 0, _root); + } + + PUGI__FN xml_node::attribute_iterator xml_node::attributes_end() const + { + return attribute_iterator(0, _root); + } + + PUGI__FN xml_object_range xml_node::children() const + { + return xml_object_range(begin(), end()); + } + + PUGI__FN xml_object_range xml_node::children(const char_t* name_) const + { + return xml_object_range(xml_named_node_iterator(child(name_)._root, _root, name_), xml_named_node_iterator(0, _root, name_)); + } + + PUGI__FN xml_object_range xml_node::attributes() const + { + return xml_object_range(attributes_begin(), attributes_end()); + } + + PUGI__FN bool xml_node::operator==(const xml_node& r) const + { + return (_root == r._root); + } + + PUGI__FN bool xml_node::operator!=(const xml_node& r) const + { + return (_root != r._root); + } + + PUGI__FN bool xml_node::operator<(const xml_node& r) const + { + return (_root < r._root); + } + + PUGI__FN bool xml_node::operator>(const xml_node& r) const + { + return (_root > r._root); + } + + PUGI__FN bool xml_node::operator<=(const xml_node& r) const + { + return (_root <= r._root); + } + + PUGI__FN bool xml_node::operator>=(const xml_node& r) const + { + return (_root >= r._root); + } + + PUGI__FN bool xml_node::empty() const + { + return !_root; + } + + PUGI__FN const char_t* xml_node::name() const + { + return (_root && _root->name) ? _root->name : PUGIXML_TEXT(""); + } + + PUGI__FN xml_node_type xml_node::type() const + { + return _root ? static_cast((_root->header & impl::xml_memory_page_type_mask) + 1) : node_null; + } + + PUGI__FN const char_t* xml_node::value() const + { + return (_root && _root->value) ? _root->value : PUGIXML_TEXT(""); + } + + PUGI__FN xml_node xml_node::child(const char_t* name_) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + + return xml_node(); + } + + PUGI__FN xml_attribute xml_node::attribute(const char_t* name_) const + { + if (!_root) return xml_attribute(); + + for (xml_attribute_struct* i = _root->first_attribute; i; i = i->next_attribute) + if (i->name && impl::strequal(name_, i->name)) + return xml_attribute(i); + + return xml_attribute(); + } + + PUGI__FN xml_node xml_node::next_sibling(const char_t* name_) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->next_sibling; i; i = i->next_sibling) + if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + + return xml_node(); + } + + PUGI__FN xml_node xml_node::next_sibling() const + { + if (!_root) return xml_node(); + + if (_root->next_sibling) return xml_node(_root->next_sibling); + else return xml_node(); + } + + PUGI__FN xml_node xml_node::previous_sibling(const char_t* name_) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->prev_sibling_c; i->next_sibling; i = i->prev_sibling_c) + if (i->name && impl::strequal(name_, i->name)) return xml_node(i); + + return xml_node(); + } + + PUGI__FN xml_node xml_node::previous_sibling() const + { + if (!_root) return xml_node(); + + if (_root->prev_sibling_c->next_sibling) return xml_node(_root->prev_sibling_c); + else return xml_node(); + } + + PUGI__FN xml_node xml_node::parent() const + { + return _root ? xml_node(_root->parent) : xml_node(); + } + + PUGI__FN xml_node xml_node::root() const + { + if (!_root) return xml_node(); + + impl::xml_memory_page* page = reinterpret_cast(_root->header & impl::xml_memory_page_pointer_mask); + + return xml_node(static_cast(page->allocator)); + } + + PUGI__FN xml_text xml_node::text() const + { + return xml_text(_root); + } + + PUGI__FN const char_t* xml_node::child_value() const + { + if (!_root) return PUGIXML_TEXT(""); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (i->value && impl::is_text_node(i)) + return i->value; + + return PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* xml_node::child_value(const char_t* name_) const + { + return child(name_).child_value(); + } + + PUGI__FN xml_attribute xml_node::first_attribute() const + { + return _root ? xml_attribute(_root->first_attribute) : xml_attribute(); + } + + PUGI__FN xml_attribute xml_node::last_attribute() const + { + return _root && _root->first_attribute ? xml_attribute(_root->first_attribute->prev_attribute_c) : xml_attribute(); + } + + PUGI__FN xml_node xml_node::first_child() const + { + return _root ? xml_node(_root->first_child) : xml_node(); + } + + PUGI__FN xml_node xml_node::last_child() const + { + return _root && _root->first_child ? xml_node(_root->first_child->prev_sibling_c) : xml_node(); + } + + PUGI__FN bool xml_node::set_name(const char_t* rhs) + { + switch (type()) + { + case node_pi: + case node_declaration: + case node_element: + return impl::strcpy_insitu(_root->name, _root->header, impl::xml_memory_page_name_allocated_mask, rhs); + + default: + return false; + } + } + + PUGI__FN bool xml_node::set_value(const char_t* rhs) + { + switch (type()) + { + case node_pi: + case node_cdata: + case node_pcdata: + case node_comment: + case node_doctype: + return impl::strcpy_insitu(_root->value, _root->header, impl::xml_memory_page_value_allocated_mask, rhs); + + default: + return false; + } + } + + PUGI__FN xml_attribute xml_node::append_attribute(const char_t* name_) + { + if (type() != node_element && type() != node_declaration) return xml_attribute(); + + xml_attribute a(impl::append_attribute_ll(_root, impl::get_allocator(_root))); + a.set_name(name_); + + return a; + } + + PUGI__FN xml_attribute xml_node::prepend_attribute(const char_t* name_) + { + if (type() != node_element && type() != node_declaration) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); + if (!a) return xml_attribute(); + + a.set_name(name_); + + xml_attribute_struct* head = _root->first_attribute; + + if (head) + { + a._attr->prev_attribute_c = head->prev_attribute_c; + head->prev_attribute_c = a._attr; + } + else + a._attr->prev_attribute_c = a._attr; + + a._attr->next_attribute = head; + _root->first_attribute = a._attr; + + return a; + } + + PUGI__FN xml_attribute xml_node::insert_attribute_before(const char_t* name_, const xml_attribute& attr) + { + if ((type() != node_element && type() != node_declaration) || attr.empty()) return xml_attribute(); + + // check that attribute belongs to *this + xml_attribute_struct* cur = attr._attr; + + while (cur->prev_attribute_c->next_attribute) cur = cur->prev_attribute_c; + + if (cur != _root->first_attribute) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); + if (!a) return xml_attribute(); + + a.set_name(name_); + + if (attr._attr->prev_attribute_c->next_attribute) + attr._attr->prev_attribute_c->next_attribute = a._attr; + else + _root->first_attribute = a._attr; + + a._attr->prev_attribute_c = attr._attr->prev_attribute_c; + a._attr->next_attribute = attr._attr; + attr._attr->prev_attribute_c = a._attr; + + return a; + } + + PUGI__FN xml_attribute xml_node::insert_attribute_after(const char_t* name_, const xml_attribute& attr) + { + if ((type() != node_element && type() != node_declaration) || attr.empty()) return xml_attribute(); + + // check that attribute belongs to *this + xml_attribute_struct* cur = attr._attr; + + while (cur->prev_attribute_c->next_attribute) cur = cur->prev_attribute_c; + + if (cur != _root->first_attribute) return xml_attribute(); + + xml_attribute a(impl::allocate_attribute(impl::get_allocator(_root))); + if (!a) return xml_attribute(); + + a.set_name(name_); + + if (attr._attr->next_attribute) + attr._attr->next_attribute->prev_attribute_c = a._attr; + else + _root->first_attribute->prev_attribute_c = a._attr; + + a._attr->next_attribute = attr._attr->next_attribute; + a._attr->prev_attribute_c = attr._attr; + attr._attr->next_attribute = a._attr; + + return a; + } + + PUGI__FN xml_attribute xml_node::append_copy(const xml_attribute& proto) + { + if (!proto) return xml_attribute(); + + xml_attribute result = append_attribute(proto.name()); + result.set_value(proto.value()); + + return result; + } + + PUGI__FN xml_attribute xml_node::prepend_copy(const xml_attribute& proto) + { + if (!proto) return xml_attribute(); + + xml_attribute result = prepend_attribute(proto.name()); + result.set_value(proto.value()); + + return result; + } + + PUGI__FN xml_attribute xml_node::insert_copy_after(const xml_attribute& proto, const xml_attribute& attr) + { + if (!proto) return xml_attribute(); + + xml_attribute result = insert_attribute_after(proto.name(), attr); + result.set_value(proto.value()); + + return result; + } + + PUGI__FN xml_attribute xml_node::insert_copy_before(const xml_attribute& proto, const xml_attribute& attr) + { + if (!proto) return xml_attribute(); + + xml_attribute result = insert_attribute_before(proto.name(), attr); + result.set_value(proto.value()); + + return result; + } + + PUGI__FN xml_node xml_node::append_child(xml_node_type type_) + { + if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); + + xml_node n(impl::append_node(_root, impl::get_allocator(_root), type_)); + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::prepend_child(xml_node_type type_) + { + if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); + + xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); + if (!n) return xml_node(); + + n._root->parent = _root; + + xml_node_struct* head = _root->first_child; + + if (head) + { + n._root->prev_sibling_c = head->prev_sibling_c; + head->prev_sibling_c = n._root; + } + else + n._root->prev_sibling_c = n._root; + + n._root->next_sibling = head; + _root->first_child = n._root; + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::insert_child_before(xml_node_type type_, const xml_node& node) + { + if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + + xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); + if (!n) return xml_node(); + + n._root->parent = _root; + + if (node._root->prev_sibling_c->next_sibling) + node._root->prev_sibling_c->next_sibling = n._root; + else + _root->first_child = n._root; + + n._root->prev_sibling_c = node._root->prev_sibling_c; + n._root->next_sibling = node._root; + node._root->prev_sibling_c = n._root; + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::insert_child_after(xml_node_type type_, const xml_node& node) + { + if (!impl::allow_insert_child(this->type(), type_)) return xml_node(); + if (!node._root || node._root->parent != _root) return xml_node(); + + xml_node n(impl::allocate_node(impl::get_allocator(_root), type_)); + if (!n) return xml_node(); + + n._root->parent = _root; + + if (node._root->next_sibling) + node._root->next_sibling->prev_sibling_c = n._root; + else + _root->first_child->prev_sibling_c = n._root; + + n._root->next_sibling = node._root->next_sibling; + n._root->prev_sibling_c = node._root; + node._root->next_sibling = n._root; + + if (type_ == node_declaration) n.set_name(PUGIXML_TEXT("xml")); + + return n; + } + + PUGI__FN xml_node xml_node::append_child(const char_t* name_) + { + xml_node result = append_child(node_element); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::prepend_child(const char_t* name_) + { + xml_node result = prepend_child(node_element); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::insert_child_after(const char_t* name_, const xml_node& node) + { + xml_node result = insert_child_after(node_element, node); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::insert_child_before(const char_t* name_, const xml_node& node) + { + xml_node result = insert_child_before(node_element, node); + + result.set_name(name_); + + return result; + } + + PUGI__FN xml_node xml_node::append_copy(const xml_node& proto) + { + xml_node result = append_child(proto.type()); + + if (result) impl::recursive_copy_skip(result, proto, result); + + return result; + } + + PUGI__FN xml_node xml_node::prepend_copy(const xml_node& proto) + { + xml_node result = prepend_child(proto.type()); + + if (result) impl::recursive_copy_skip(result, proto, result); + + return result; + } + + PUGI__FN xml_node xml_node::insert_copy_after(const xml_node& proto, const xml_node& node) + { + xml_node result = insert_child_after(proto.type(), node); + + if (result) impl::recursive_copy_skip(result, proto, result); + + return result; + } + + PUGI__FN xml_node xml_node::insert_copy_before(const xml_node& proto, const xml_node& node) + { + xml_node result = insert_child_before(proto.type(), node); + + if (result) impl::recursive_copy_skip(result, proto, result); + + return result; + } + + PUGI__FN bool xml_node::remove_attribute(const char_t* name_) + { + return remove_attribute(attribute(name_)); + } + + PUGI__FN bool xml_node::remove_attribute(const xml_attribute& a) + { + if (!_root || !a._attr) return false; + + // check that attribute belongs to *this + xml_attribute_struct* attr = a._attr; + + while (attr->prev_attribute_c->next_attribute) attr = attr->prev_attribute_c; + + if (attr != _root->first_attribute) return false; + + if (a._attr->next_attribute) a._attr->next_attribute->prev_attribute_c = a._attr->prev_attribute_c; + else if (_root->first_attribute) _root->first_attribute->prev_attribute_c = a._attr->prev_attribute_c; + + if (a._attr->prev_attribute_c->next_attribute) a._attr->prev_attribute_c->next_attribute = a._attr->next_attribute; + else _root->first_attribute = a._attr->next_attribute; + + impl::destroy_attribute(a._attr, impl::get_allocator(_root)); + + return true; + } + + PUGI__FN bool xml_node::remove_child(const char_t* name_) + { + return remove_child(child(name_)); + } + + PUGI__FN bool xml_node::remove_child(const xml_node& n) + { + if (!_root || !n._root || n._root->parent != _root) return false; + + if (n._root->next_sibling) n._root->next_sibling->prev_sibling_c = n._root->prev_sibling_c; + else if (_root->first_child) _root->first_child->prev_sibling_c = n._root->prev_sibling_c; + + if (n._root->prev_sibling_c->next_sibling) n._root->prev_sibling_c->next_sibling = n._root->next_sibling; + else _root->first_child = n._root->next_sibling; + + impl::destroy_node(n._root, impl::get_allocator(_root)); + + return true; + } + + PUGI__FN xml_parse_result xml_node::append_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + // append_buffer is only valid for elements/documents + if (!impl::allow_insert_child(type(), node_element)) return impl::make_parse_result(status_append_invalid_root); + + // get document node + impl::xml_document_struct* doc = static_cast(root()._root); + assert(doc); + + // get extra buffer element (we'll store the document fragment buffer there so that we can deallocate it later) + impl::xml_memory_page* page = 0; + impl::xml_extra_buffer* extra = static_cast(doc->allocate_memory(sizeof(impl::xml_extra_buffer), page)); + (void)page; + + if (!extra) return impl::make_parse_result(status_out_of_memory); + + // save name; name of the root has to be NULL before parsing - otherwise closing node mismatches will not be detected at the top level + char_t* rootname = _root->name; + _root->name = 0; + + // parse + char_t* buffer = 0; + xml_parse_result res = impl::load_buffer_impl(doc, _root, const_cast(contents), size, options, encoding, false, false, &buffer); + + // restore name + _root->name = rootname; + + // add extra buffer to the list + extra->buffer = buffer; + extra->next = doc->extra_buffers; + doc->extra_buffers = extra; + + return res; + } + + PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* name_, const char_t* attr_name, const char_t* attr_value) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if (i->name && impl::strequal(name_, i->name)) + { + for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) + if (a->name && impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value ? a->value : PUGIXML_TEXT(""))) + return xml_node(i); + } + + return xml_node(); + } + + PUGI__FN xml_node xml_node::find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const + { + if (!_root) return xml_node(); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + for (xml_attribute_struct* a = i->first_attribute; a; a = a->next_attribute) + if (a->name && impl::strequal(attr_name, a->name) && impl::strequal(attr_value, a->value ? a->value : PUGIXML_TEXT(""))) + return xml_node(i); + + return xml_node(); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN string_t xml_node::path(char_t delimiter) const + { + xml_node cursor = *this; // Make a copy. + + string_t result = cursor.name(); + + while (cursor.parent()) + { + cursor = cursor.parent(); + + string_t temp = cursor.name(); + temp += delimiter; + temp += result; + result.swap(temp); + } + + return result; + } +#endif + + PUGI__FN xml_node xml_node::first_element_by_path(const char_t* path_, char_t delimiter) const + { + xml_node found = *this; // Current search context. + + if (!_root || !path_ || !path_[0]) return found; + + if (path_[0] == delimiter) + { + // Absolute path; e.g. '/foo/bar' + found = found.root(); + ++path_; + } + + const char_t* path_segment = path_; + + while (*path_segment == delimiter) ++path_segment; + + const char_t* path_segment_end = path_segment; + + while (*path_segment_end && *path_segment_end != delimiter) ++path_segment_end; + + if (path_segment == path_segment_end) return found; + + const char_t* next_segment = path_segment_end; + + while (*next_segment == delimiter) ++next_segment; + + if (*path_segment == '.' && path_segment + 1 == path_segment_end) + return found.first_element_by_path(next_segment, delimiter); + else if (*path_segment == '.' && *(path_segment+1) == '.' && path_segment + 2 == path_segment_end) + return found.parent().first_element_by_path(next_segment, delimiter); + else + { + for (xml_node_struct* j = found._root->first_child; j; j = j->next_sibling) + { + if (j->name && impl::strequalrange(j->name, path_segment, static_cast(path_segment_end - path_segment))) + { + xml_node subsearch = xml_node(j).first_element_by_path(next_segment, delimiter); + + if (subsearch) return subsearch; + } + } + + return xml_node(); + } + } + + PUGI__FN bool xml_node::traverse(xml_tree_walker& walker) + { + walker._depth = -1; + + xml_node arg_begin = *this; + if (!walker.begin(arg_begin)) return false; + + xml_node cur = first_child(); + + if (cur) + { + ++walker._depth; + + do + { + xml_node arg_for_each = cur; + if (!walker.for_each(arg_for_each)) + return false; + + if (cur.first_child()) + { + ++walker._depth; + cur = cur.first_child(); + } + else if (cur.next_sibling()) + cur = cur.next_sibling(); + else + { + // Borland C++ workaround + while (!cur.next_sibling() && cur != *this && !cur.parent().empty()) + { + --walker._depth; + cur = cur.parent(); + } + + if (cur != *this) + cur = cur.next_sibling(); + } + } + while (cur && cur != *this); + } + + assert(walker._depth == -1); + + xml_node arg_end = *this; + return walker.end(arg_end); + } + + PUGI__FN size_t xml_node::hash_value() const + { + return static_cast(reinterpret_cast(_root) / sizeof(xml_node_struct)); + } + + PUGI__FN xml_node_struct* xml_node::internal_object() const + { + return _root; + } + + PUGI__FN void xml_node::print(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const + { + if (!_root) return; + + impl::xml_buffered_writer buffered_writer(writer, encoding); + + impl::node_output(buffered_writer, *this, indent, flags, depth); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding, unsigned int depth) const + { + xml_writer_stream writer(stream); + + print(writer, indent, flags, encoding, depth); + } + + PUGI__FN void xml_node::print(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, unsigned int depth) const + { + xml_writer_stream writer(stream); + + print(writer, indent, flags, encoding_wchar, depth); + } +#endif + + PUGI__FN ptrdiff_t xml_node::offset_debug() const + { + xml_node_struct* r = root()._root; + + if (!r) return -1; + + const char_t* buffer = static_cast(r)->buffer; + + if (!buffer) return -1; + + switch (type()) + { + case node_document: + return 0; + + case node_element: + case node_declaration: + case node_pi: + return (_root->header & impl::xml_memory_page_name_allocated_mask) ? -1 : _root->name - buffer; + + case node_pcdata: + case node_cdata: + case node_comment: + case node_doctype: + return (_root->header & impl::xml_memory_page_value_allocated_mask) ? -1 : _root->value - buffer; + + default: + return -1; + } + } + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xml_node& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xml_node& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN xml_text::xml_text(xml_node_struct* root): _root(root) + { + } + + PUGI__FN xml_node_struct* xml_text::_data() const + { + if (!_root || impl::is_text_node(_root)) return _root; + + for (xml_node_struct* node = _root->first_child; node; node = node->next_sibling) + if (impl::is_text_node(node)) + return node; + + return 0; + } + + PUGI__FN xml_node_struct* xml_text::_data_new() + { + xml_node_struct* d = _data(); + if (d) return d; + + return xml_node(_root).append_child(node_pcdata).internal_object(); + } + + PUGI__FN xml_text::xml_text(): _root(0) + { + } + + PUGI__FN static void unspecified_bool_xml_text(xml_text***) + { + } + + PUGI__FN xml_text::operator xml_text::unspecified_bool_type() const + { + return _data() ? unspecified_bool_xml_text : 0; + } + + PUGI__FN bool xml_text::operator!() const + { + return !_data(); + } + + PUGI__FN bool xml_text::empty() const + { + return _data() == 0; + } + + PUGI__FN const char_t* xml_text::get() const + { + xml_node_struct* d = _data(); + + return (d && d->value) ? d->value : PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* xml_text::as_string(const char_t* def) const + { + xml_node_struct* d = _data(); + + return (d && d->value) ? d->value : def; + } + + PUGI__FN int xml_text::as_int(int def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_int(d ? d->value : 0, def); + } + + PUGI__FN unsigned int xml_text::as_uint(unsigned int def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_uint(d ? d->value : 0, def); + } + + PUGI__FN double xml_text::as_double(double def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_double(d ? d->value : 0, def); + } + + PUGI__FN float xml_text::as_float(float def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_float(d ? d->value : 0, def); + } + + PUGI__FN bool xml_text::as_bool(bool def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_bool(d ? d->value : 0, def); + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN long long xml_text::as_llong(long long def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_llong(d ? d->value : 0, def); + } + + PUGI__FN unsigned long long xml_text::as_ullong(unsigned long long def) const + { + xml_node_struct* d = _data(); + + return impl::get_value_ullong(d ? d->value : 0, def); + } +#endif + + PUGI__FN bool xml_text::set(const char_t* rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::strcpy_insitu(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(int rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(unsigned int rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(double rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(bool rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN bool xml_text::set(long long rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } + + PUGI__FN bool xml_text::set(unsigned long long rhs) + { + xml_node_struct* dn = _data_new(); + + return dn ? impl::set_value_convert(dn->value, dn->header, impl::xml_memory_page_value_allocated_mask, rhs) : false; + } +#endif + + PUGI__FN xml_text& xml_text::operator=(const char_t* rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(int rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(unsigned int rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(double rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(bool rhs) + { + set(rhs); + return *this; + } + +#ifdef PUGIXML_HAS_LONG_LONG + PUGI__FN xml_text& xml_text::operator=(long long rhs) + { + set(rhs); + return *this; + } + + PUGI__FN xml_text& xml_text::operator=(unsigned long long rhs) + { + set(rhs); + return *this; + } +#endif + + PUGI__FN xml_node xml_text::data() const + { + return xml_node(_data()); + } + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xml_text& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xml_text& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN xml_node_iterator::xml_node_iterator() + { + } + + PUGI__FN xml_node_iterator::xml_node_iterator(const xml_node& node): _wrap(node), _parent(node.parent()) + { + } + + PUGI__FN xml_node_iterator::xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) + { + } + + PUGI__FN bool xml_node_iterator::operator==(const xml_node_iterator& rhs) const + { + return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; + } + + PUGI__FN bool xml_node_iterator::operator!=(const xml_node_iterator& rhs) const + { + return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; + } + + PUGI__FN xml_node& xml_node_iterator::operator*() const + { + assert(_wrap._root); + return _wrap; + } + + PUGI__FN xml_node* xml_node_iterator::operator->() const + { + assert(_wrap._root); + return const_cast(&_wrap); // BCC32 workaround + } + + PUGI__FN const xml_node_iterator& xml_node_iterator::operator++() + { + assert(_wrap._root); + _wrap._root = _wrap._root->next_sibling; + return *this; + } + + PUGI__FN xml_node_iterator xml_node_iterator::operator++(int) + { + xml_node_iterator temp = *this; + ++*this; + return temp; + } + + PUGI__FN const xml_node_iterator& xml_node_iterator::operator--() + { + _wrap = _wrap._root ? _wrap.previous_sibling() : _parent.last_child(); + return *this; + } + + PUGI__FN xml_node_iterator xml_node_iterator::operator--(int) + { + xml_node_iterator temp = *this; + --*this; + return temp; + } + + PUGI__FN xml_attribute_iterator::xml_attribute_iterator() + { + } + + PUGI__FN xml_attribute_iterator::xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent): _wrap(attr), _parent(parent) + { + } + + PUGI__FN xml_attribute_iterator::xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent): _wrap(ref), _parent(parent) + { + } + + PUGI__FN bool xml_attribute_iterator::operator==(const xml_attribute_iterator& rhs) const + { + return _wrap._attr == rhs._wrap._attr && _parent._root == rhs._parent._root; + } + + PUGI__FN bool xml_attribute_iterator::operator!=(const xml_attribute_iterator& rhs) const + { + return _wrap._attr != rhs._wrap._attr || _parent._root != rhs._parent._root; + } + + PUGI__FN xml_attribute& xml_attribute_iterator::operator*() const + { + assert(_wrap._attr); + return _wrap; + } + + PUGI__FN xml_attribute* xml_attribute_iterator::operator->() const + { + assert(_wrap._attr); + return const_cast(&_wrap); // BCC32 workaround + } + + PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator++() + { + assert(_wrap._attr); + _wrap._attr = _wrap._attr->next_attribute; + return *this; + } + + PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator++(int) + { + xml_attribute_iterator temp = *this; + ++*this; + return temp; + } + + PUGI__FN const xml_attribute_iterator& xml_attribute_iterator::operator--() + { + _wrap = _wrap._attr ? _wrap.previous_attribute() : _parent.last_attribute(); + return *this; + } + + PUGI__FN xml_attribute_iterator xml_attribute_iterator::operator--(int) + { + xml_attribute_iterator temp = *this; + --*this; + return temp; + } + + PUGI__FN xml_named_node_iterator::xml_named_node_iterator(): _name(0) + { + } + + PUGI__FN xml_named_node_iterator::xml_named_node_iterator(const xml_node& node, const char_t* name): _wrap(node), _parent(node.parent()), _name(name) + { + } + + PUGI__FN xml_named_node_iterator::xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name): _wrap(ref), _parent(parent), _name(name) + { + } + + PUGI__FN bool xml_named_node_iterator::operator==(const xml_named_node_iterator& rhs) const + { + return _wrap._root == rhs._wrap._root && _parent._root == rhs._parent._root; + } + + PUGI__FN bool xml_named_node_iterator::operator!=(const xml_named_node_iterator& rhs) const + { + return _wrap._root != rhs._wrap._root || _parent._root != rhs._parent._root; + } + + PUGI__FN xml_node& xml_named_node_iterator::operator*() const + { + assert(_wrap._root); + return _wrap; + } + + PUGI__FN xml_node* xml_named_node_iterator::operator->() const + { + assert(_wrap._root); + return const_cast(&_wrap); // BCC32 workaround + } + + PUGI__FN const xml_named_node_iterator& xml_named_node_iterator::operator++() + { + assert(_wrap._root); + _wrap = _wrap.next_sibling(_name); + return *this; + } + + PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator++(int) + { + xml_named_node_iterator temp = *this; + ++*this; + return temp; + } + + PUGI__FN const xml_named_node_iterator& xml_named_node_iterator::operator--() + { + if (_wrap._root) + _wrap = _wrap.previous_sibling(_name); + else + { + _wrap = _parent.last_child(); + + if (!impl::strequal(_wrap.name(), _name)) + _wrap = _wrap.previous_sibling(_name); + } + + return *this; + } + + PUGI__FN xml_named_node_iterator xml_named_node_iterator::operator--(int) + { + xml_named_node_iterator temp = *this; + --*this; + return temp; + } + + PUGI__FN xml_parse_result::xml_parse_result(): status(status_internal_error), offset(0), encoding(encoding_auto) + { + } + + PUGI__FN xml_parse_result::operator bool() const + { + return status == status_ok; + } + + PUGI__FN const char* xml_parse_result::description() const + { + switch (status) + { + case status_ok: return "No error"; + + case status_file_not_found: return "File was not found"; + case status_io_error: return "Error reading from file/stream"; + case status_out_of_memory: return "Could not allocate memory"; + case status_internal_error: return "Internal error occurred"; + + case status_unrecognized_tag: return "Could not determine tag type"; + + case status_bad_pi: return "Error parsing document declaration/processing instruction"; + case status_bad_comment: return "Error parsing comment"; + case status_bad_cdata: return "Error parsing CDATA section"; + case status_bad_doctype: return "Error parsing document type declaration"; + case status_bad_pcdata: return "Error parsing PCDATA section"; + case status_bad_start_element: return "Error parsing start element tag"; + case status_bad_attribute: return "Error parsing element attribute"; + case status_bad_end_element: return "Error parsing end element tag"; + case status_end_element_mismatch: return "Start-end tags mismatch"; + + case status_append_invalid_root: return "Unable to append nodes: root is not an element or document"; + + case status_no_document_element: return "No document element found"; + + default: return "Unknown error"; + } + } + + PUGI__FN xml_document::xml_document(): _buffer(0) + { + create(); + } + + PUGI__FN xml_document::~xml_document() + { + destroy(); + } + + PUGI__FN void xml_document::reset() + { + destroy(); + create(); + } + + PUGI__FN void xml_document::reset(const xml_document& proto) + { + reset(); + + for (xml_node cur = proto.first_child(); cur; cur = cur.next_sibling()) + append_copy(cur); + } + + PUGI__FN void xml_document::create() + { + assert(!_root); + + // initialize sentinel page + PUGI__STATIC_ASSERT(sizeof(impl::xml_memory_page) + sizeof(impl::xml_document_struct) + impl::xml_memory_page_alignment <= sizeof(_memory)); + + // align upwards to page boundary + void* page_memory = reinterpret_cast((reinterpret_cast(_memory) + (impl::xml_memory_page_alignment - 1)) & ~(impl::xml_memory_page_alignment - 1)); + + // prepare page structure + impl::xml_memory_page* page = impl::xml_memory_page::construct(page_memory); + assert(page); + + page->busy_size = impl::xml_memory_page_size; + + // allocate new root + _root = new (page->data) impl::xml_document_struct(page); + _root->prev_sibling_c = _root; + + // setup sentinel page + page->allocator = static_cast(_root); + } + + PUGI__FN void xml_document::destroy() + { + assert(_root); + + // destroy static storage + if (_buffer) + { + impl::xml_memory::deallocate(_buffer); + _buffer = 0; + } + + // destroy extra buffers (note: no need to destroy linked list nodes, they're allocated using document allocator) + for (impl::xml_extra_buffer* extra = static_cast(_root)->extra_buffers; extra; extra = extra->next) + { + if (extra->buffer) impl::xml_memory::deallocate(extra->buffer); + } + + // destroy dynamic storage, leave sentinel page (it's in static memory) + impl::xml_memory_page* root_page = reinterpret_cast(_root->header & impl::xml_memory_page_pointer_mask); + assert(root_page && !root_page->prev && !root_page->memory); + + for (impl::xml_memory_page* page = root_page->next; page; ) + { + impl::xml_memory_page* next = page->next; + + impl::xml_allocator::deallocate_page(page); + + page = next; + } + + _root = 0; + } + +#ifndef PUGIXML_NO_STL + PUGI__FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_stream_impl(*this, stream, options, encoding); + } + + PUGI__FN xml_parse_result xml_document::load(std::basic_istream >& stream, unsigned int options) + { + reset(); + + return impl::load_stream_impl(*this, stream, options, encoding_wchar); + } +#endif + + PUGI__FN xml_parse_result xml_document::load(const char_t* contents, unsigned int options) + { + // Force native encoding (skip autodetection) + #ifdef PUGIXML_WCHAR_MODE + xml_encoding encoding = encoding_wchar; + #else + xml_encoding encoding = encoding_utf8; + #endif + + return load_buffer(contents, impl::strlength(contents) * sizeof(char_t), options, encoding); + } + + PUGI__FN xml_parse_result xml_document::load_file(const char* path_, unsigned int options, xml_encoding encoding) + { + reset(); + + FILE* file = fopen(path_, "rb"); + + return impl::load_file_impl(*this, file, options, encoding); + } + + PUGI__FN xml_parse_result xml_document::load_file(const wchar_t* path_, unsigned int options, xml_encoding encoding) + { + reset(); + + FILE* file = impl::open_file_wide(path_, L"rb"); + + return impl::load_file_impl(*this, file, options, encoding); + } + + PUGI__FN xml_parse_result xml_document::load_buffer(const void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_buffer_impl(static_cast(_root), _root, const_cast(contents), size, options, encoding, false, false, &_buffer); + } + + PUGI__FN xml_parse_result xml_document::load_buffer_inplace(void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_buffer_impl(static_cast(_root), _root, contents, size, options, encoding, true, false, &_buffer); + } + + PUGI__FN xml_parse_result xml_document::load_buffer_inplace_own(void* contents, size_t size, unsigned int options, xml_encoding encoding) + { + reset(); + + return impl::load_buffer_impl(static_cast(_root), _root, contents, size, options, encoding, true, true, &_buffer); + } + + PUGI__FN void xml_document::save(xml_writer& writer, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + impl::xml_buffered_writer buffered_writer(writer, encoding); + + if ((flags & format_write_bom) && encoding != encoding_latin1) + { + // BOM always represents the codepoint U+FEFF, so just write it in native encoding + #ifdef PUGIXML_WCHAR_MODE + unsigned int bom = 0xfeff; + buffered_writer.write(static_cast(bom)); + #else + buffered_writer.write('\xef', '\xbb', '\xbf'); + #endif + } + + if (!(flags & format_no_declaration) && !impl::has_declaration(*this)) + { + buffered_writer.write(PUGIXML_TEXT("'); + if (!(flags & format_raw)) buffered_writer.write('\n'); + } + + impl::node_output(buffered_writer, *this, indent, flags, 0); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + xml_writer_stream writer(stream); + + save(writer, indent, flags, encoding); + } + + PUGI__FN void xml_document::save(std::basic_ostream >& stream, const char_t* indent, unsigned int flags) const + { + xml_writer_stream writer(stream); + + save(writer, indent, flags, encoding_wchar); + } +#endif + + PUGI__FN bool xml_document::save_file(const char* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + FILE* file = fopen(path_, (flags & format_save_file_text) ? "w" : "wb"); + return impl::save_file_impl(*this, file, indent, flags, encoding); + } + + PUGI__FN bool xml_document::save_file(const wchar_t* path_, const char_t* indent, unsigned int flags, xml_encoding encoding) const + { + FILE* file = impl::open_file_wide(path_, (flags & format_save_file_text) ? L"w" : L"wb"); + return impl::save_file_impl(*this, file, indent, flags, encoding); + } + + PUGI__FN xml_node xml_document::document_element() const + { + assert(_root); + + for (xml_node_struct* i = _root->first_child; i; i = i->next_sibling) + if ((i->header & impl::xml_memory_page_type_mask) + 1 == node_element) + return xml_node(i); + + return xml_node(); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const wchar_t* str) + { + assert(str); + + return impl::as_utf8_impl(str, impl::strlength_wide(str)); + } + + PUGI__FN std::string PUGIXML_FUNCTION as_utf8(const std::basic_string& str) + { + return impl::as_utf8_impl(str.c_str(), str.size()); + } + + PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const char* str) + { + assert(str); + + return impl::as_wide_impl(str, strlen(str)); + } + + PUGI__FN std::basic_string PUGIXML_FUNCTION as_wide(const std::string& str) + { + return impl::as_wide_impl(str.c_str(), str.size()); + } +#endif + + PUGI__FN void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate) + { + impl::xml_memory::allocate = allocate; + impl::xml_memory::deallocate = deallocate; + } + + PUGI__FN allocation_function PUGIXML_FUNCTION get_memory_allocation_function() + { + return impl::xml_memory::allocate; + } + + PUGI__FN deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function() + { + return impl::xml_memory::deallocate; + } +} + +#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) +namespace std +{ + // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) + PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_attribute_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag _Iter_cat(const pugi::xml_named_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } +} +#endif + +#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) +namespace std +{ + // Workarounds for (non-standard) iterator category detection + PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_attribute_iterator&) + { + return std::bidirectional_iterator_tag(); + } + + PUGI__FN std::bidirectional_iterator_tag __iterator_category(const pugi::xml_named_node_iterator&) + { + return std::bidirectional_iterator_tag(); + } +} +#endif + +#ifndef PUGIXML_NO_XPATH + +// STL replacements +PUGI__NS_BEGIN + struct equal_to + { + template bool operator()(const T& lhs, const T& rhs) const + { + return lhs == rhs; + } + }; + + struct not_equal_to + { + template bool operator()(const T& lhs, const T& rhs) const + { + return lhs != rhs; + } + }; + + struct less + { + template bool operator()(const T& lhs, const T& rhs) const + { + return lhs < rhs; + } + }; + + struct less_equal + { + template bool operator()(const T& lhs, const T& rhs) const + { + return lhs <= rhs; + } + }; + + template void swap(T& lhs, T& rhs) + { + T temp = lhs; + lhs = rhs; + rhs = temp; + } + + template I min_element(I begin, I end, const Pred& pred) + { + I result = begin; + + for (I it = begin + 1; it != end; ++it) + if (pred(*it, *result)) + result = it; + + return result; + } + + template void reverse(I begin, I end) + { + while (end - begin > 1) swap(*begin++, *--end); + } + + template I unique(I begin, I end) + { + // fast skip head + while (end - begin > 1 && *begin != *(begin + 1)) begin++; + + if (begin == end) return begin; + + // last written element + I write = begin++; + + // merge unique elements + while (begin != end) + { + if (*begin != *write) + *++write = *begin++; + else + begin++; + } + + // past-the-end (write points to live element) + return write + 1; + } + + template void copy_backwards(I begin, I end, I target) + { + while (begin != end) *--target = *--end; + } + + template void insertion_sort(I begin, I end, const Pred& pred, T*) + { + assert(begin != end); + + for (I it = begin + 1; it != end; ++it) + { + T val = *it; + + if (pred(val, *begin)) + { + // move to front + copy_backwards(begin, it, it + 1); + *begin = val; + } + else + { + I hole = it; + + // move hole backwards + while (pred(val, *(hole - 1))) + { + *hole = *(hole - 1); + hole--; + } + + // fill hole with element + *hole = val; + } + } + } + + // std variant for elements with == + template void partition(I begin, I middle, I end, const Pred& pred, I* out_eqbeg, I* out_eqend) + { + I eqbeg = middle, eqend = middle + 1; + + // expand equal range + while (eqbeg != begin && *(eqbeg - 1) == *eqbeg) --eqbeg; + while (eqend != end && *eqend == *eqbeg) ++eqend; + + // process outer elements + I ltend = eqbeg, gtbeg = eqend; + + for (;;) + { + // find the element from the right side that belongs to the left one + for (; gtbeg != end; ++gtbeg) + if (!pred(*eqbeg, *gtbeg)) + { + if (*gtbeg == *eqbeg) swap(*gtbeg, *eqend++); + else break; + } + + // find the element from the left side that belongs to the right one + for (; ltend != begin; --ltend) + if (!pred(*(ltend - 1), *eqbeg)) + { + if (*eqbeg == *(ltend - 1)) swap(*(ltend - 1), *--eqbeg); + else break; + } + + // scanned all elements + if (gtbeg == end && ltend == begin) + { + *out_eqbeg = eqbeg; + *out_eqend = eqend; + return; + } + + // make room for elements by moving equal area + if (gtbeg == end) + { + if (--ltend != --eqbeg) swap(*ltend, *eqbeg); + swap(*eqbeg, *--eqend); + } + else if (ltend == begin) + { + if (eqend != gtbeg) swap(*eqbeg, *eqend); + ++eqend; + swap(*gtbeg++, *eqbeg++); + } + else swap(*gtbeg++, *--ltend); + } + } + + template void median3(I first, I middle, I last, const Pred& pred) + { + if (pred(*middle, *first)) swap(*middle, *first); + if (pred(*last, *middle)) swap(*last, *middle); + if (pred(*middle, *first)) swap(*middle, *first); + } + + template void median(I first, I middle, I last, const Pred& pred) + { + if (last - first <= 40) + { + // median of three for small chunks + median3(first, middle, last, pred); + } + else + { + // median of nine + size_t step = (last - first + 1) / 8; + + median3(first, first + step, first + 2 * step, pred); + median3(middle - step, middle, middle + step, pred); + median3(last - 2 * step, last - step, last, pred); + median3(first + step, middle, last - step, pred); + } + } + + template void sort(I begin, I end, const Pred& pred) + { + // sort large chunks + while (end - begin > 32) + { + // find median element + I middle = begin + (end - begin) / 2; + median(begin, middle, end - 1, pred); + + // partition in three chunks (< = >) + I eqbeg, eqend; + partition(begin, middle, end, pred, &eqbeg, &eqend); + + // loop on larger half + if (eqbeg - begin > end - eqend) + { + sort(eqend, end, pred); + end = eqbeg; + } + else + { + sort(begin, eqbeg, pred); + begin = eqend; + } + } + + // insertion sort small chunk + if (begin != end) insertion_sort(begin, end, pred, &*begin); + } +PUGI__NS_END + +// Allocator used for AST and evaluation stacks +PUGI__NS_BEGIN + struct xpath_memory_block + { + xpath_memory_block* next; + + char data[ + #ifdef PUGIXML_MEMORY_XPATH_PAGE_SIZE + PUGIXML_MEMORY_XPATH_PAGE_SIZE + #else + 4096 + #endif + ]; + }; + + class xpath_allocator + { + xpath_memory_block* _root; + size_t _root_size; + + public: + #ifdef PUGIXML_NO_EXCEPTIONS + jmp_buf* error_handler; + #endif + + xpath_allocator(xpath_memory_block* root, size_t root_size = 0): _root(root), _root_size(root_size) + { + #ifdef PUGIXML_NO_EXCEPTIONS + error_handler = 0; + #endif + } + + void* allocate_nothrow(size_t size) + { + const size_t block_capacity = sizeof(_root->data); + + // align size so that we're able to store pointers in subsequent blocks + size = (size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); + + if (_root_size + size <= block_capacity) + { + void* buf = _root->data + _root_size; + _root_size += size; + return buf; + } + else + { + size_t block_data_size = (size > block_capacity) ? size : block_capacity; + size_t block_size = block_data_size + offsetof(xpath_memory_block, data); + + xpath_memory_block* block = static_cast(xml_memory::allocate(block_size)); + if (!block) return 0; + + block->next = _root; + + _root = block; + _root_size = size; + + return block->data; + } + } + + void* allocate(size_t size) + { + void* result = allocate_nothrow(size); + + if (!result) + { + #ifdef PUGIXML_NO_EXCEPTIONS + assert(error_handler); + longjmp(*error_handler, 1); + #else + throw std::bad_alloc(); + #endif + } + + return result; + } + + void* reallocate(void* ptr, size_t old_size, size_t new_size) + { + // align size so that we're able to store pointers in subsequent blocks + old_size = (old_size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); + new_size = (new_size + sizeof(void*) - 1) & ~(sizeof(void*) - 1); + + // we can only reallocate the last object + assert(ptr == 0 || static_cast(ptr) + old_size == _root->data + _root_size); + + // adjust root size so that we have not allocated the object at all + bool only_object = (_root_size == old_size); + + if (ptr) _root_size -= old_size; + + // allocate a new version (this will obviously reuse the memory if possible) + void* result = allocate(new_size); + assert(result); + + // we have a new block + if (result != ptr && ptr) + { + // copy old data + assert(new_size >= old_size); + memcpy(result, ptr, old_size); + + // free the previous page if it had no other objects + if (only_object) + { + assert(_root->data == result); + assert(_root->next); + + xpath_memory_block* next = _root->next->next; + + if (next) + { + // deallocate the whole page, unless it was the first one + xml_memory::deallocate(_root->next); + _root->next = next; + } + } + } + + return result; + } + + void revert(const xpath_allocator& state) + { + // free all new pages + xpath_memory_block* cur = _root; + + while (cur != state._root) + { + xpath_memory_block* next = cur->next; + + xml_memory::deallocate(cur); + + cur = next; + } + + // restore state + _root = state._root; + _root_size = state._root_size; + } + + void release() + { + xpath_memory_block* cur = _root; + assert(cur); + + while (cur->next) + { + xpath_memory_block* next = cur->next; + + xml_memory::deallocate(cur); + + cur = next; + } + } + }; + + struct xpath_allocator_capture + { + xpath_allocator_capture(xpath_allocator* alloc): _target(alloc), _state(*alloc) + { + } + + ~xpath_allocator_capture() + { + _target->revert(_state); + } + + xpath_allocator* _target; + xpath_allocator _state; + }; + + struct xpath_stack + { + xpath_allocator* result; + xpath_allocator* temp; + }; + + struct xpath_stack_data + { + xpath_memory_block blocks[2]; + xpath_allocator result; + xpath_allocator temp; + xpath_stack stack; + + #ifdef PUGIXML_NO_EXCEPTIONS + jmp_buf error_handler; + #endif + + xpath_stack_data(): result(blocks + 0), temp(blocks + 1) + { + blocks[0].next = blocks[1].next = 0; + + stack.result = &result; + stack.temp = &temp; + + #ifdef PUGIXML_NO_EXCEPTIONS + result.error_handler = temp.error_handler = &error_handler; + #endif + } + + ~xpath_stack_data() + { + result.release(); + temp.release(); + } + }; +PUGI__NS_END + +// String class +PUGI__NS_BEGIN + class xpath_string + { + const char_t* _buffer; + bool _uses_heap; + + static char_t* duplicate_string(const char_t* string, size_t length, xpath_allocator* alloc) + { + char_t* result = static_cast(alloc->allocate((length + 1) * sizeof(char_t))); + assert(result); + + memcpy(result, string, length * sizeof(char_t)); + result[length] = 0; + + return result; + } + + static char_t* duplicate_string(const char_t* string, xpath_allocator* alloc) + { + return duplicate_string(string, strlength(string), alloc); + } + + public: + xpath_string(): _buffer(PUGIXML_TEXT("")), _uses_heap(false) + { + } + + explicit xpath_string(const char_t* str, xpath_allocator* alloc) + { + bool empty_ = (*str == 0); + + _buffer = empty_ ? PUGIXML_TEXT("") : duplicate_string(str, alloc); + _uses_heap = !empty_; + } + + explicit xpath_string(const char_t* str, bool use_heap): _buffer(str), _uses_heap(use_heap) + { + } + + xpath_string(const char_t* begin, const char_t* end, xpath_allocator* alloc) + { + assert(begin <= end); + + bool empty_ = (begin == end); + + _buffer = empty_ ? PUGIXML_TEXT("") : duplicate_string(begin, static_cast(end - begin), alloc); + _uses_heap = !empty_; + } + + void append(const xpath_string& o, xpath_allocator* alloc) + { + // skip empty sources + if (!*o._buffer) return; + + // fast append for constant empty target and constant source + if (!*_buffer && !_uses_heap && !o._uses_heap) + { + _buffer = o._buffer; + } + else + { + // need to make heap copy + size_t target_length = strlength(_buffer); + size_t source_length = strlength(o._buffer); + size_t result_length = target_length + source_length; + + // allocate new buffer + char_t* result = static_cast(alloc->reallocate(_uses_heap ? const_cast(_buffer) : 0, (target_length + 1) * sizeof(char_t), (result_length + 1) * sizeof(char_t))); + assert(result); + + // append first string to the new buffer in case there was no reallocation + if (!_uses_heap) memcpy(result, _buffer, target_length * sizeof(char_t)); + + // append second string to the new buffer + memcpy(result + target_length, o._buffer, source_length * sizeof(char_t)); + result[result_length] = 0; + + // finalize + _buffer = result; + _uses_heap = true; + } + } + + const char_t* c_str() const + { + return _buffer; + } + + size_t length() const + { + return strlength(_buffer); + } + + char_t* data(xpath_allocator* alloc) + { + // make private heap copy + if (!_uses_heap) + { + _buffer = duplicate_string(_buffer, alloc); + _uses_heap = true; + } + + return const_cast(_buffer); + } + + bool empty() const + { + return *_buffer == 0; + } + + bool operator==(const xpath_string& o) const + { + return strequal(_buffer, o._buffer); + } + + bool operator!=(const xpath_string& o) const + { + return !strequal(_buffer, o._buffer); + } + + bool uses_heap() const + { + return _uses_heap; + } + }; + + PUGI__FN xpath_string xpath_string_const(const char_t* str) + { + return xpath_string(str, false); + } +PUGI__NS_END + +PUGI__NS_BEGIN + PUGI__FN bool starts_with(const char_t* string, const char_t* pattern) + { + while (*pattern && *string == *pattern) + { + string++; + pattern++; + } + + return *pattern == 0; + } + + PUGI__FN const char_t* find_char(const char_t* s, char_t c) + { + #ifdef PUGIXML_WCHAR_MODE + return wcschr(s, c); + #else + return strchr(s, c); + #endif + } + + PUGI__FN const char_t* find_substring(const char_t* s, const char_t* p) + { + #ifdef PUGIXML_WCHAR_MODE + // MSVC6 wcsstr bug workaround (if s is empty it always returns 0) + return (*p == 0) ? s : wcsstr(s, p); + #else + return strstr(s, p); + #endif + } + + // Converts symbol to lower case, if it is an ASCII one + PUGI__FN char_t tolower_ascii(char_t ch) + { + return static_cast(ch - 'A') < 26 ? static_cast(ch | ' ') : ch; + } + + PUGI__FN xpath_string string_value(const xpath_node& na, xpath_allocator* alloc) + { + if (na.attribute()) + return xpath_string_const(na.attribute().value()); + else + { + const xml_node& n = na.node(); + + switch (n.type()) + { + case node_pcdata: + case node_cdata: + case node_comment: + case node_pi: + return xpath_string_const(n.value()); + + case node_document: + case node_element: + { + xpath_string result; + + xml_node cur = n.first_child(); + + while (cur && cur != n) + { + if (cur.type() == node_pcdata || cur.type() == node_cdata) + result.append(xpath_string_const(cur.value()), alloc); + + if (cur.first_child()) + cur = cur.first_child(); + else if (cur.next_sibling()) + cur = cur.next_sibling(); + else + { + while (!cur.next_sibling() && cur != n) + cur = cur.parent(); + + if (cur != n) cur = cur.next_sibling(); + } + } + + return result; + } + + default: + return xpath_string(); + } + } + } + + PUGI__FN unsigned int node_height(xml_node n) + { + unsigned int result = 0; + + while (n) + { + ++result; + n = n.parent(); + } + + return result; + } + + PUGI__FN bool node_is_before(xml_node ln, unsigned int lh, xml_node rn, unsigned int rh) + { + // normalize heights + for (unsigned int i = rh; i < lh; i++) ln = ln.parent(); + for (unsigned int j = lh; j < rh; j++) rn = rn.parent(); + + // one node is the ancestor of the other + if (ln == rn) return lh < rh; + + // find common ancestor + while (ln.parent() != rn.parent()) + { + ln = ln.parent(); + rn = rn.parent(); + } + + // there is no common ancestor (the shared parent is null), nodes are from different documents + if (!ln.parent()) return ln < rn; + + // determine sibling order + for (; ln; ln = ln.next_sibling()) + if (ln == rn) + return true; + + return false; + } + + PUGI__FN bool node_is_ancestor(xml_node parent, xml_node node) + { + while (node && node != parent) node = node.parent(); + + return parent && node == parent; + } + + PUGI__FN const void* document_order(const xpath_node& xnode) + { + xml_node_struct* node = xnode.node().internal_object(); + + if (node) + { + if (node->name && (node->header & xml_memory_page_name_allocated_mask) == 0) return node->name; + if (node->value && (node->header & xml_memory_page_value_allocated_mask) == 0) return node->value; + return 0; + } + + xml_attribute_struct* attr = xnode.attribute().internal_object(); + + if (attr) + { + if ((attr->header & xml_memory_page_name_allocated_mask) == 0) return attr->name; + if ((attr->header & xml_memory_page_value_allocated_mask) == 0) return attr->value; + return 0; + } + + return 0; + } + + struct document_order_comparator + { + bool operator()(const xpath_node& lhs, const xpath_node& rhs) const + { + // optimized document order based check + const void* lo = document_order(lhs); + const void* ro = document_order(rhs); + + if (lo && ro) return lo < ro; + + // slow comparison + xml_node ln = lhs.node(), rn = rhs.node(); + + // compare attributes + if (lhs.attribute() && rhs.attribute()) + { + // shared parent + if (lhs.parent() == rhs.parent()) + { + // determine sibling order + for (xml_attribute a = lhs.attribute(); a; a = a.next_attribute()) + if (a == rhs.attribute()) + return true; + + return false; + } + + // compare attribute parents + ln = lhs.parent(); + rn = rhs.parent(); + } + else if (lhs.attribute()) + { + // attributes go after the parent element + if (lhs.parent() == rhs.node()) return false; + + ln = lhs.parent(); + } + else if (rhs.attribute()) + { + // attributes go after the parent element + if (rhs.parent() == lhs.node()) return true; + + rn = rhs.parent(); + } + + if (ln == rn) return false; + + unsigned int lh = node_height(ln); + unsigned int rh = node_height(rn); + + return node_is_before(ln, lh, rn, rh); + } + }; + + struct duplicate_comparator + { + bool operator()(const xpath_node& lhs, const xpath_node& rhs) const + { + if (lhs.attribute()) return rhs.attribute() ? lhs.attribute() < rhs.attribute() : true; + else return rhs.attribute() ? false : lhs.node() < rhs.node(); + } + }; + + PUGI__FN double gen_nan() + { + #if defined(__STDC_IEC_559__) || ((FLT_RADIX - 0 == 2) && (FLT_MAX_EXP - 0 == 128) && (FLT_MANT_DIG - 0 == 24)) + union { float f; uint32_t i; } u[sizeof(float) == sizeof(uint32_t) ? 1 : -1]; + u[0].i = 0x7fc00000; + return u[0].f; + #else + // fallback + const volatile double zero = 0.0; + return zero / zero; + #endif + } + + PUGI__FN bool is_nan(double value) + { + #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) + return !!_isnan(value); + #elif defined(fpclassify) && defined(FP_NAN) + return fpclassify(value) == FP_NAN; + #else + // fallback + const volatile double v = value; + return v != v; + #endif + } + + PUGI__FN const char_t* convert_number_to_string_special(double value) + { + #if defined(PUGI__MSVC_CRT_VERSION) || defined(__BORLANDC__) + if (_finite(value)) return (value == 0) ? PUGIXML_TEXT("0") : 0; + if (_isnan(value)) return PUGIXML_TEXT("NaN"); + return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); + #elif defined(fpclassify) && defined(FP_NAN) && defined(FP_INFINITE) && defined(FP_ZERO) + switch (fpclassify(value)) + { + case FP_NAN: + return PUGIXML_TEXT("NaN"); + + case FP_INFINITE: + return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); + + case FP_ZERO: + return PUGIXML_TEXT("0"); + + default: + return 0; + } + #else + // fallback + const volatile double v = value; + + if (v == 0) return PUGIXML_TEXT("0"); + if (v != v) return PUGIXML_TEXT("NaN"); + if (v * 2 == v) return value > 0 ? PUGIXML_TEXT("Infinity") : PUGIXML_TEXT("-Infinity"); + return 0; + #endif + } + + PUGI__FN bool convert_number_to_boolean(double value) + { + return (value != 0 && !is_nan(value)); + } + + PUGI__FN void truncate_zeros(char* begin, char* end) + { + while (begin != end && end[-1] == '0') end--; + + *end = 0; + } + + // gets mantissa digits in the form of 0.xxxxx with 0. implied and the exponent +#if defined(PUGI__MSVC_CRT_VERSION) && PUGI__MSVC_CRT_VERSION >= 1400 && !defined(_WIN32_WCE) + PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent) + { + // get base values + int sign, exponent; + _ecvt_s(buffer, buffer_size, value, DBL_DIG + 1, &exponent, &sign); + + // truncate redundant zeros + truncate_zeros(buffer, buffer + strlen(buffer)); + + // fill results + *out_mantissa = buffer; + *out_exponent = exponent; + } +#else + PUGI__FN void convert_number_to_mantissa_exponent(double value, char* buffer, size_t buffer_size, char** out_mantissa, int* out_exponent) + { + // get a scientific notation value with IEEE DBL_DIG decimals + sprintf(buffer, "%.*e", DBL_DIG, value); + assert(strlen(buffer) < buffer_size); + (void)!buffer_size; + + // get the exponent (possibly negative) + char* exponent_string = strchr(buffer, 'e'); + assert(exponent_string); + + int exponent = atoi(exponent_string + 1); + + // extract mantissa string: skip sign + char* mantissa = buffer[0] == '-' ? buffer + 1 : buffer; + assert(mantissa[0] != '0' && mantissa[1] == '.'); + + // divide mantissa by 10 to eliminate integer part + mantissa[1] = mantissa[0]; + mantissa++; + exponent++; + + // remove extra mantissa digits and zero-terminate mantissa + truncate_zeros(mantissa, exponent_string); + + // fill results + *out_mantissa = mantissa; + *out_exponent = exponent; + } +#endif + + PUGI__FN xpath_string convert_number_to_string(double value, xpath_allocator* alloc) + { + // try special number conversion + const char_t* special = convert_number_to_string_special(value); + if (special) return xpath_string_const(special); + + // get mantissa + exponent form + char mantissa_buffer[32]; + + char* mantissa; + int exponent; + convert_number_to_mantissa_exponent(value, mantissa_buffer, sizeof(mantissa_buffer), &mantissa, &exponent); + + // allocate a buffer of suitable length for the number + size_t result_size = strlen(mantissa_buffer) + (exponent > 0 ? exponent : -exponent) + 4; + char_t* result = static_cast(alloc->allocate(sizeof(char_t) * result_size)); + assert(result); + + // make the number! + char_t* s = result; + + // sign + if (value < 0) *s++ = '-'; + + // integer part + if (exponent <= 0) + { + *s++ = '0'; + } + else + { + while (exponent > 0) + { + assert(*mantissa == 0 || static_cast(static_cast(*mantissa) - '0') <= 9); + *s++ = *mantissa ? *mantissa++ : '0'; + exponent--; + } + } + + // fractional part + if (*mantissa) + { + // decimal point + *s++ = '.'; + + // extra zeroes from negative exponent + while (exponent < 0) + { + *s++ = '0'; + exponent++; + } + + // extra mantissa digits + while (*mantissa) + { + assert(static_cast(*mantissa - '0') <= 9); + *s++ = *mantissa++; + } + } + + // zero-terminate + assert(s < result + result_size); + *s = 0; + + return xpath_string(result, true); + } + + PUGI__FN bool check_string_to_number_format(const char_t* string) + { + // parse leading whitespace + while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; + + // parse sign + if (*string == '-') ++string; + + if (!*string) return false; + + // if there is no integer part, there should be a decimal part with at least one digit + if (!PUGI__IS_CHARTYPEX(string[0], ctx_digit) && (string[0] != '.' || !PUGI__IS_CHARTYPEX(string[1], ctx_digit))) return false; + + // parse integer part + while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; + + // parse decimal part + if (*string == '.') + { + ++string; + + while (PUGI__IS_CHARTYPEX(*string, ctx_digit)) ++string; + } + + // parse trailing whitespace + while (PUGI__IS_CHARTYPE(*string, ct_space)) ++string; + + return *string == 0; + } + + PUGI__FN double convert_string_to_number(const char_t* string) + { + // check string format + if (!check_string_to_number_format(string)) return gen_nan(); + + // parse string + #ifdef PUGIXML_WCHAR_MODE + return wcstod(string, 0); + #else + return atof(string); + #endif + } + + PUGI__FN bool convert_string_to_number_scratch(char_t (&buffer)[32], const char_t* begin, const char_t* end, double* out_result) + { + size_t length = static_cast(end - begin); + char_t* scratch = buffer; + + if (length >= sizeof(buffer) / sizeof(buffer[0])) + { + // need to make dummy on-heap copy + scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!scratch) return false; + } + + // copy string to zero-terminated buffer and perform conversion + memcpy(scratch, begin, length * sizeof(char_t)); + scratch[length] = 0; + + *out_result = convert_string_to_number(scratch); + + // free dummy buffer + if (scratch != buffer) xml_memory::deallocate(scratch); + + return true; + } + + PUGI__FN double round_nearest(double value) + { + return floor(value + 0.5); + } + + PUGI__FN double round_nearest_nzero(double value) + { + // same as round_nearest, but returns -0 for [-0.5, -0] + // ceil is used to differentiate between +0 and -0 (we return -0 for [-0.5, -0] and +0 for +0) + return (value >= -0.5 && value <= 0) ? ceil(value) : floor(value + 0.5); + } + + PUGI__FN const char_t* qualified_name(const xpath_node& node) + { + return node.attribute() ? node.attribute().name() : node.node().name(); + } + + PUGI__FN const char_t* local_name(const xpath_node& node) + { + const char_t* name = qualified_name(node); + const char_t* p = find_char(name, ':'); + + return p ? p + 1 : name; + } + + struct namespace_uri_predicate + { + const char_t* prefix; + size_t prefix_length; + + namespace_uri_predicate(const char_t* name) + { + const char_t* pos = find_char(name, ':'); + + prefix = pos ? name : 0; + prefix_length = pos ? static_cast(pos - name) : 0; + } + + bool operator()(const xml_attribute& a) const + { + const char_t* name = a.name(); + + if (!starts_with(name, PUGIXML_TEXT("xmlns"))) return false; + + return prefix ? name[5] == ':' && strequalrange(name + 6, prefix, prefix_length) : name[5] == 0; + } + }; + + PUGI__FN const char_t* namespace_uri(const xml_node& node) + { + namespace_uri_predicate pred = node.name(); + + xml_node p = node; + + while (p) + { + xml_attribute a = p.find_attribute(pred); + + if (a) return a.value(); + + p = p.parent(); + } + + return PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* namespace_uri(const xml_attribute& attr, const xml_node& parent) + { + namespace_uri_predicate pred = attr.name(); + + // Default namespace does not apply to attributes + if (!pred.prefix) return PUGIXML_TEXT(""); + + xml_node p = parent; + + while (p) + { + xml_attribute a = p.find_attribute(pred); + + if (a) return a.value(); + + p = p.parent(); + } + + return PUGIXML_TEXT(""); + } + + PUGI__FN const char_t* namespace_uri(const xpath_node& node) + { + return node.attribute() ? namespace_uri(node.attribute(), node.parent()) : namespace_uri(node.node()); + } + + PUGI__FN void normalize_space(char_t* buffer) + { + char_t* write = buffer; + + for (char_t* it = buffer; *it; ) + { + char_t ch = *it++; + + if (PUGI__IS_CHARTYPE(ch, ct_space)) + { + // replace whitespace sequence with single space + while (PUGI__IS_CHARTYPE(*it, ct_space)) it++; + + // avoid leading spaces + if (write != buffer) *write++ = ' '; + } + else *write++ = ch; + } + + // remove trailing space + if (write != buffer && PUGI__IS_CHARTYPE(write[-1], ct_space)) write--; + + // zero-terminate + *write = 0; + } + + PUGI__FN void translate(char_t* buffer, const char_t* from, const char_t* to) + { + size_t to_length = strlength(to); + + char_t* write = buffer; + + while (*buffer) + { + PUGI__DMC_VOLATILE char_t ch = *buffer++; + + const char_t* pos = find_char(from, ch); + + if (!pos) + *write++ = ch; // do not process + else if (static_cast(pos - from) < to_length) + *write++ = to[pos - from]; // replace + } + + // zero-terminate + *write = 0; + } + + struct xpath_variable_boolean: xpath_variable + { + xpath_variable_boolean(): value(false) + { + } + + bool value; + char_t name[1]; + }; + + struct xpath_variable_number: xpath_variable + { + xpath_variable_number(): value(0) + { + } + + double value; + char_t name[1]; + }; + + struct xpath_variable_string: xpath_variable + { + xpath_variable_string(): value(0) + { + } + + ~xpath_variable_string() + { + if (value) xml_memory::deallocate(value); + } + + char_t* value; + char_t name[1]; + }; + + struct xpath_variable_node_set: xpath_variable + { + xpath_node_set value; + char_t name[1]; + }; + + static const xpath_node_set dummy_node_set; + + PUGI__FN unsigned int hash_string(const char_t* str) + { + // Jenkins one-at-a-time hash (http://en.wikipedia.org/wiki/Jenkins_hash_function#one-at-a-time) + unsigned int result = 0; + + while (*str) + { + result += static_cast(*str++); + result += result << 10; + result ^= result >> 6; + } + + result += result << 3; + result ^= result >> 11; + result += result << 15; + + return result; + } + + template PUGI__FN T* new_xpath_variable(const char_t* name) + { + size_t length = strlength(name); + if (length == 0) return 0; // empty variable names are invalid + + // $$ we can't use offsetof(T, name) because T is non-POD, so we just allocate additional length characters + void* memory = xml_memory::allocate(sizeof(T) + length * sizeof(char_t)); + if (!memory) return 0; + + T* result = new (memory) T(); + + memcpy(result->name, name, (length + 1) * sizeof(char_t)); + + return result; + } + + PUGI__FN xpath_variable* new_xpath_variable(xpath_value_type type, const char_t* name) + { + switch (type) + { + case xpath_type_node_set: + return new_xpath_variable(name); + + case xpath_type_number: + return new_xpath_variable(name); + + case xpath_type_string: + return new_xpath_variable(name); + + case xpath_type_boolean: + return new_xpath_variable(name); + + default: + return 0; + } + } + + template PUGI__FN void delete_xpath_variable(T* var) + { + var->~T(); + xml_memory::deallocate(var); + } + + PUGI__FN void delete_xpath_variable(xpath_value_type type, xpath_variable* var) + { + switch (type) + { + case xpath_type_node_set: + delete_xpath_variable(static_cast(var)); + break; + + case xpath_type_number: + delete_xpath_variable(static_cast(var)); + break; + + case xpath_type_string: + delete_xpath_variable(static_cast(var)); + break; + + case xpath_type_boolean: + delete_xpath_variable(static_cast(var)); + break; + + default: + assert(!"Invalid variable type"); + } + } + + PUGI__FN xpath_variable* get_variable_scratch(char_t (&buffer)[32], xpath_variable_set* set, const char_t* begin, const char_t* end) + { + size_t length = static_cast(end - begin); + char_t* scratch = buffer; + + if (length >= sizeof(buffer) / sizeof(buffer[0])) + { + // need to make dummy on-heap copy + scratch = static_cast(xml_memory::allocate((length + 1) * sizeof(char_t))); + if (!scratch) return 0; + } + + // copy string to zero-terminated buffer and perform lookup + memcpy(scratch, begin, length * sizeof(char_t)); + scratch[length] = 0; + + xpath_variable* result = set->get(scratch); + + // free dummy buffer + if (scratch != buffer) xml_memory::deallocate(scratch); + + return result; + } +PUGI__NS_END + +// Internal node set class +PUGI__NS_BEGIN + PUGI__FN xpath_node_set::type_t xpath_sort(xpath_node* begin, xpath_node* end, xpath_node_set::type_t type, bool rev) + { + xpath_node_set::type_t order = rev ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted; + + if (type == xpath_node_set::type_unsorted) + { + sort(begin, end, document_order_comparator()); + + type = xpath_node_set::type_sorted; + } + + if (type != order) reverse(begin, end); + + return order; + } + + PUGI__FN xpath_node xpath_first(const xpath_node* begin, const xpath_node* end, xpath_node_set::type_t type) + { + if (begin == end) return xpath_node(); + + switch (type) + { + case xpath_node_set::type_sorted: + return *begin; + + case xpath_node_set::type_sorted_reverse: + return *(end - 1); + + case xpath_node_set::type_unsorted: + return *min_element(begin, end, document_order_comparator()); + + default: + assert(!"Invalid node set type"); + return xpath_node(); + } + } + + class xpath_node_set_raw + { + xpath_node_set::type_t _type; + + xpath_node* _begin; + xpath_node* _end; + xpath_node* _eos; + + public: + xpath_node_set_raw(): _type(xpath_node_set::type_unsorted), _begin(0), _end(0), _eos(0) + { + } + + xpath_node* begin() const + { + return _begin; + } + + xpath_node* end() const + { + return _end; + } + + bool empty() const + { + return _begin == _end; + } + + size_t size() const + { + return static_cast(_end - _begin); + } + + xpath_node first() const + { + return xpath_first(_begin, _end, _type); + } + + void push_back(const xpath_node& node, xpath_allocator* alloc) + { + if (_end == _eos) + { + size_t capacity = static_cast(_eos - _begin); + + // get new capacity (1.5x rule) + size_t new_capacity = capacity + capacity / 2 + 1; + + // reallocate the old array or allocate a new one + xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), new_capacity * sizeof(xpath_node))); + assert(data); + + // finalize + _begin = data; + _end = data + capacity; + _eos = data + new_capacity; + } + + *_end++ = node; + } + + void append(const xpath_node* begin_, const xpath_node* end_, xpath_allocator* alloc) + { + size_t size_ = static_cast(_end - _begin); + size_t capacity = static_cast(_eos - _begin); + size_t count = static_cast(end_ - begin_); + + if (size_ + count > capacity) + { + // reallocate the old array or allocate a new one + xpath_node* data = static_cast(alloc->reallocate(_begin, capacity * sizeof(xpath_node), (size_ + count) * sizeof(xpath_node))); + assert(data); + + // finalize + _begin = data; + _end = data + size_; + _eos = data + size_ + count; + } + + memcpy(_end, begin_, count * sizeof(xpath_node)); + _end += count; + } + + void sort_do() + { + _type = xpath_sort(_begin, _end, _type, false); + } + + void truncate(xpath_node* pos) + { + assert(_begin <= pos && pos <= _end); + + _end = pos; + } + + void remove_duplicates() + { + if (_type == xpath_node_set::type_unsorted) + sort(_begin, _end, duplicate_comparator()); + + _end = unique(_begin, _end); + } + + xpath_node_set::type_t type() const + { + return _type; + } + + void set_type(xpath_node_set::type_t value) + { + _type = value; + } + }; +PUGI__NS_END + +PUGI__NS_BEGIN + struct xpath_context + { + xpath_node n; + size_t position, size; + + xpath_context(const xpath_node& n_, size_t position_, size_t size_): n(n_), position(position_), size(size_) + { + } + }; + + enum lexeme_t + { + lex_none = 0, + lex_equal, + lex_not_equal, + lex_less, + lex_greater, + lex_less_or_equal, + lex_greater_or_equal, + lex_plus, + lex_minus, + lex_multiply, + lex_union, + lex_var_ref, + lex_open_brace, + lex_close_brace, + lex_quoted_string, + lex_number, + lex_slash, + lex_double_slash, + lex_open_square_brace, + lex_close_square_brace, + lex_string, + lex_comma, + lex_axis_attribute, + lex_dot, + lex_double_dot, + lex_double_colon, + lex_eof + }; + + struct xpath_lexer_string + { + const char_t* begin; + const char_t* end; + + xpath_lexer_string(): begin(0), end(0) + { + } + + bool operator==(const char_t* other) const + { + size_t length = static_cast(end - begin); + + return strequalrange(other, begin, length); + } + }; + + class xpath_lexer + { + const char_t* _cur; + const char_t* _cur_lexeme_pos; + xpath_lexer_string _cur_lexeme_contents; + + lexeme_t _cur_lexeme; + + public: + explicit xpath_lexer(const char_t* query): _cur(query) + { + next(); + } + + const char_t* state() const + { + return _cur; + } + + void next() + { + const char_t* cur = _cur; + + while (PUGI__IS_CHARTYPE(*cur, ct_space)) ++cur; + + // save lexeme position for error reporting + _cur_lexeme_pos = cur; + + switch (*cur) + { + case 0: + _cur_lexeme = lex_eof; + break; + + case '>': + if (*(cur+1) == '=') + { + cur += 2; + _cur_lexeme = lex_greater_or_equal; + } + else + { + cur += 1; + _cur_lexeme = lex_greater; + } + break; + + case '<': + if (*(cur+1) == '=') + { + cur += 2; + _cur_lexeme = lex_less_or_equal; + } + else + { + cur += 1; + _cur_lexeme = lex_less; + } + break; + + case '!': + if (*(cur+1) == '=') + { + cur += 2; + _cur_lexeme = lex_not_equal; + } + else + { + _cur_lexeme = lex_none; + } + break; + + case '=': + cur += 1; + _cur_lexeme = lex_equal; + + break; + + case '+': + cur += 1; + _cur_lexeme = lex_plus; + + break; + + case '-': + cur += 1; + _cur_lexeme = lex_minus; + + break; + + case '*': + cur += 1; + _cur_lexeme = lex_multiply; + + break; + + case '|': + cur += 1; + _cur_lexeme = lex_union; + + break; + + case '$': + cur += 1; + + if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) + { + _cur_lexeme_contents.begin = cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + + if (cur[0] == ':' && PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // qname + { + cur++; // : + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + } + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_var_ref; + } + else + { + _cur_lexeme = lex_none; + } + + break; + + case '(': + cur += 1; + _cur_lexeme = lex_open_brace; + + break; + + case ')': + cur += 1; + _cur_lexeme = lex_close_brace; + + break; + + case '[': + cur += 1; + _cur_lexeme = lex_open_square_brace; + + break; + + case ']': + cur += 1; + _cur_lexeme = lex_close_square_brace; + + break; + + case ',': + cur += 1; + _cur_lexeme = lex_comma; + + break; + + case '/': + if (*(cur+1) == '/') + { + cur += 2; + _cur_lexeme = lex_double_slash; + } + else + { + cur += 1; + _cur_lexeme = lex_slash; + } + break; + + case '.': + if (*(cur+1) == '.') + { + cur += 2; + _cur_lexeme = lex_double_dot; + } + else if (PUGI__IS_CHARTYPEX(*(cur+1), ctx_digit)) + { + _cur_lexeme_contents.begin = cur; // . + + ++cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_number; + } + else + { + cur += 1; + _cur_lexeme = lex_dot; + } + break; + + case '@': + cur += 1; + _cur_lexeme = lex_axis_attribute; + + break; + + case '"': + case '\'': + { + char_t terminator = *cur; + + ++cur; + + _cur_lexeme_contents.begin = cur; + while (*cur && *cur != terminator) cur++; + _cur_lexeme_contents.end = cur; + + if (!*cur) + _cur_lexeme = lex_none; + else + { + cur += 1; + _cur_lexeme = lex_quoted_string; + } + + break; + } + + case ':': + if (*(cur+1) == ':') + { + cur += 2; + _cur_lexeme = lex_double_colon; + } + else + { + _cur_lexeme = lex_none; + } + break; + + default: + if (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) + { + _cur_lexeme_contents.begin = cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + + if (*cur == '.') + { + cur++; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_digit)) cur++; + } + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_number; + } + else if (PUGI__IS_CHARTYPEX(*cur, ctx_start_symbol)) + { + _cur_lexeme_contents.begin = cur; + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + + if (cur[0] == ':') + { + if (cur[1] == '*') // namespace test ncname:* + { + cur += 2; // :* + } + else if (PUGI__IS_CHARTYPEX(cur[1], ctx_symbol)) // namespace test qname + { + cur++; // : + + while (PUGI__IS_CHARTYPEX(*cur, ctx_symbol)) cur++; + } + } + + _cur_lexeme_contents.end = cur; + + _cur_lexeme = lex_string; + } + else + { + _cur_lexeme = lex_none; + } + } + + _cur = cur; + } + + lexeme_t current() const + { + return _cur_lexeme; + } + + const char_t* current_pos() const + { + return _cur_lexeme_pos; + } + + const xpath_lexer_string& contents() const + { + assert(_cur_lexeme == lex_var_ref || _cur_lexeme == lex_number || _cur_lexeme == lex_string || _cur_lexeme == lex_quoted_string); + + return _cur_lexeme_contents; + } + }; + + enum ast_type_t + { + ast_unknown, + ast_op_or, // left or right + ast_op_and, // left and right + ast_op_equal, // left = right + ast_op_not_equal, // left != right + ast_op_less, // left < right + ast_op_greater, // left > right + ast_op_less_or_equal, // left <= right + ast_op_greater_or_equal, // left >= right + ast_op_add, // left + right + ast_op_subtract, // left - right + ast_op_multiply, // left * right + ast_op_divide, // left / right + ast_op_mod, // left % right + ast_op_negate, // left - right + ast_op_union, // left | right + ast_predicate, // apply predicate to set; next points to next predicate + ast_filter, // select * from left where right + ast_filter_posinv, // select * from left where right; proximity position invariant + ast_string_constant, // string constant + ast_number_constant, // number constant + ast_variable, // variable + ast_func_last, // last() + ast_func_position, // position() + ast_func_count, // count(left) + ast_func_id, // id(left) + ast_func_local_name_0, // local-name() + ast_func_local_name_1, // local-name(left) + ast_func_namespace_uri_0, // namespace-uri() + ast_func_namespace_uri_1, // namespace-uri(left) + ast_func_name_0, // name() + ast_func_name_1, // name(left) + ast_func_string_0, // string() + ast_func_string_1, // string(left) + ast_func_concat, // concat(left, right, siblings) + ast_func_starts_with, // starts_with(left, right) + ast_func_contains, // contains(left, right) + ast_func_substring_before, // substring-before(left, right) + ast_func_substring_after, // substring-after(left, right) + ast_func_substring_2, // substring(left, right) + ast_func_substring_3, // substring(left, right, third) + ast_func_string_length_0, // string-length() + ast_func_string_length_1, // string-length(left) + ast_func_normalize_space_0, // normalize-space() + ast_func_normalize_space_1, // normalize-space(left) + ast_func_translate, // translate(left, right, third) + ast_func_boolean, // boolean(left) + ast_func_not, // not(left) + ast_func_true, // true() + ast_func_false, // false() + ast_func_lang, // lang(left) + ast_func_number_0, // number() + ast_func_number_1, // number(left) + ast_func_sum, // sum(left) + ast_func_floor, // floor(left) + ast_func_ceiling, // ceiling(left) + ast_func_round, // round(left) + ast_step, // process set left with step + ast_step_root // select root node + }; + + enum axis_t + { + axis_ancestor, + axis_ancestor_or_self, + axis_attribute, + axis_child, + axis_descendant, + axis_descendant_or_self, + axis_following, + axis_following_sibling, + axis_namespace, + axis_parent, + axis_preceding, + axis_preceding_sibling, + axis_self + }; + + enum nodetest_t + { + nodetest_none, + nodetest_name, + nodetest_type_node, + nodetest_type_comment, + nodetest_type_pi, + nodetest_type_text, + nodetest_pi, + nodetest_all, + nodetest_all_in_namespace + }; + + template struct axis_to_type + { + static const axis_t axis; + }; + + template const axis_t axis_to_type::axis = N; + + class xpath_ast_node + { + private: + // node type + char _type; + char _rettype; + + // for ast_step / ast_predicate + char _axis; + char _test; + + // tree node structure + xpath_ast_node* _left; + xpath_ast_node* _right; + xpath_ast_node* _next; + + union + { + // value for ast_string_constant + const char_t* string; + // value for ast_number_constant + double number; + // variable for ast_variable + xpath_variable* variable; + // node test for ast_step (node name/namespace/node type/pi target) + const char_t* nodetest; + } _data; + + xpath_ast_node(const xpath_ast_node&); + xpath_ast_node& operator=(const xpath_ast_node&); + + template static bool compare_eq(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) + { + xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); + + if (lt != xpath_type_node_set && rt != xpath_type_node_set) + { + if (lt == xpath_type_boolean || rt == xpath_type_boolean) + return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); + else if (lt == xpath_type_number || rt == xpath_type_number) + return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); + else if (lt == xpath_type_string || rt == xpath_type_string) + { + xpath_allocator_capture cr(stack.result); + + xpath_string ls = lhs->eval_string(c, stack); + xpath_string rs = rhs->eval_string(c, stack); + + return comp(ls, rs); + } + } + else if (lt == xpath_type_node_set && rt == xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ls = lhs->eval_node_set(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack); + + for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(string_value(*li, stack.result), string_value(*ri, stack.result))) + return true; + } + + return false; + } + else + { + if (lt == xpath_type_node_set) + { + swap(lhs, rhs); + swap(lt, rt); + } + + if (lt == xpath_type_boolean) + return comp(lhs->eval_boolean(c, stack), rhs->eval_boolean(c, stack)); + else if (lt == xpath_type_number) + { + xpath_allocator_capture cr(stack.result); + + double l = lhs->eval_number(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) + return true; + } + + return false; + } + else if (lt == xpath_type_string) + { + xpath_allocator_capture cr(stack.result); + + xpath_string l = lhs->eval_string(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(l, string_value(*ri, stack.result))) + return true; + } + + return false; + } + } + + assert(!"Wrong types"); + return false; + } + + template static bool compare_rel(xpath_ast_node* lhs, xpath_ast_node* rhs, const xpath_context& c, const xpath_stack& stack, const Comp& comp) + { + xpath_value_type lt = lhs->rettype(), rt = rhs->rettype(); + + if (lt != xpath_type_node_set && rt != xpath_type_node_set) + return comp(lhs->eval_number(c, stack), rhs->eval_number(c, stack)); + else if (lt == xpath_type_node_set && rt == xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ls = lhs->eval_node_set(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack); + + for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) + { + xpath_allocator_capture cri(stack.result); + + double l = convert_string_to_number(string_value(*li, stack.result).c_str()); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture crii(stack.result); + + if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) + return true; + } + } + + return false; + } + else if (lt != xpath_type_node_set && rt == xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + double l = lhs->eval_number(c, stack); + xpath_node_set_raw rs = rhs->eval_node_set(c, stack); + + for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri) + { + xpath_allocator_capture cri(stack.result); + + if (comp(l, convert_string_to_number(string_value(*ri, stack.result).c_str()))) + return true; + } + + return false; + } + else if (lt == xpath_type_node_set && rt != xpath_type_node_set) + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ls = lhs->eval_node_set(c, stack); + double r = rhs->eval_number(c, stack); + + for (const xpath_node* li = ls.begin(); li != ls.end(); ++li) + { + xpath_allocator_capture cri(stack.result); + + if (comp(convert_string_to_number(string_value(*li, stack.result).c_str()), r)) + return true; + } + + return false; + } + else + { + assert(!"Wrong types"); + return false; + } + } + + void apply_predicate(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack) + { + assert(ns.size() >= first); + + size_t i = 1; + size_t size = ns.size() - first; + + xpath_node* last = ns.begin() + first; + + // remove_if... or well, sort of + for (xpath_node* it = last; it != ns.end(); ++it, ++i) + { + xpath_context c(*it, i, size); + + if (expr->rettype() == xpath_type_number) + { + if (expr->eval_number(c, stack) == i) + *last++ = *it; + } + else if (expr->eval_boolean(c, stack)) + *last++ = *it; + } + + ns.truncate(last); + } + + void apply_predicates(xpath_node_set_raw& ns, size_t first, const xpath_stack& stack) + { + if (ns.size() == first) return; + + for (xpath_ast_node* pred = _right; pred; pred = pred->_next) + { + apply_predicate(ns, first, pred->_left, stack); + } + } + + void step_push(xpath_node_set_raw& ns, const xml_attribute& a, const xml_node& parent, xpath_allocator* alloc) + { + if (!a) return; + + const char_t* name = a.name(); + + // There are no attribute nodes corresponding to attributes that declare namespaces + // That is, "xmlns:..." or "xmlns" + if (starts_with(name, PUGIXML_TEXT("xmlns")) && (name[5] == 0 || name[5] == ':')) return; + + switch (_test) + { + case nodetest_name: + if (strequal(name, _data.nodetest)) ns.push_back(xpath_node(a, parent), alloc); + break; + + case nodetest_type_node: + case nodetest_all: + ns.push_back(xpath_node(a, parent), alloc); + break; + + case nodetest_all_in_namespace: + if (starts_with(name, _data.nodetest)) + ns.push_back(xpath_node(a, parent), alloc); + break; + + default: + ; + } + } + + void step_push(xpath_node_set_raw& ns, const xml_node& n, xpath_allocator* alloc) + { + if (!n) return; + + switch (_test) + { + case nodetest_name: + if (n.type() == node_element && strequal(n.name(), _data.nodetest)) ns.push_back(n, alloc); + break; + + case nodetest_type_node: + ns.push_back(n, alloc); + break; + + case nodetest_type_comment: + if (n.type() == node_comment) + ns.push_back(n, alloc); + break; + + case nodetest_type_text: + if (n.type() == node_pcdata || n.type() == node_cdata) + ns.push_back(n, alloc); + break; + + case nodetest_type_pi: + if (n.type() == node_pi) + ns.push_back(n, alloc); + break; + + case nodetest_pi: + if (n.type() == node_pi && strequal(n.name(), _data.nodetest)) + ns.push_back(n, alloc); + break; + + case nodetest_all: + if (n.type() == node_element) + ns.push_back(n, alloc); + break; + + case nodetest_all_in_namespace: + if (n.type() == node_element && starts_with(n.name(), _data.nodetest)) + ns.push_back(n, alloc); + break; + + default: + assert(!"Unknown axis"); + } + } + + template void step_fill(xpath_node_set_raw& ns, const xml_node& n, xpath_allocator* alloc, T) + { + const axis_t axis = T::axis; + + switch (axis) + { + case axis_attribute: + { + for (xml_attribute a = n.first_attribute(); a; a = a.next_attribute()) + step_push(ns, a, n, alloc); + + break; + } + + case axis_child: + { + for (xml_node c = n.first_child(); c; c = c.next_sibling()) + step_push(ns, c, alloc); + + break; + } + + case axis_descendant: + case axis_descendant_or_self: + { + if (axis == axis_descendant_or_self) + step_push(ns, n, alloc); + + xml_node cur = n.first_child(); + + while (cur && cur != n) + { + step_push(ns, cur, alloc); + + if (cur.first_child()) + cur = cur.first_child(); + else if (cur.next_sibling()) + cur = cur.next_sibling(); + else + { + while (!cur.next_sibling() && cur != n) + cur = cur.parent(); + + if (cur != n) cur = cur.next_sibling(); + } + } + + break; + } + + case axis_following_sibling: + { + for (xml_node c = n.next_sibling(); c; c = c.next_sibling()) + step_push(ns, c, alloc); + + break; + } + + case axis_preceding_sibling: + { + for (xml_node c = n.previous_sibling(); c; c = c.previous_sibling()) + step_push(ns, c, alloc); + + break; + } + + case axis_following: + { + xml_node cur = n; + + // exit from this node so that we don't include descendants + while (cur && !cur.next_sibling()) cur = cur.parent(); + cur = cur.next_sibling(); + + for (;;) + { + step_push(ns, cur, alloc); + + if (cur.first_child()) + cur = cur.first_child(); + else if (cur.next_sibling()) + cur = cur.next_sibling(); + else + { + while (cur && !cur.next_sibling()) cur = cur.parent(); + cur = cur.next_sibling(); + + if (!cur) break; + } + } + + break; + } + + case axis_preceding: + { + xml_node cur = n; + + while (cur && !cur.previous_sibling()) cur = cur.parent(); + cur = cur.previous_sibling(); + + for (;;) + { + if (cur.last_child()) + cur = cur.last_child(); + else + { + // leaf node, can't be ancestor + step_push(ns, cur, alloc); + + if (cur.previous_sibling()) + cur = cur.previous_sibling(); + else + { + do + { + cur = cur.parent(); + if (!cur) break; + + if (!node_is_ancestor(cur, n)) step_push(ns, cur, alloc); + } + while (!cur.previous_sibling()); + + cur = cur.previous_sibling(); + + if (!cur) break; + } + } + } + + break; + } + + case axis_ancestor: + case axis_ancestor_or_self: + { + if (axis == axis_ancestor_or_self) + step_push(ns, n, alloc); + + xml_node cur = n.parent(); + + while (cur) + { + step_push(ns, cur, alloc); + + cur = cur.parent(); + } + + break; + } + + case axis_self: + { + step_push(ns, n, alloc); + + break; + } + + case axis_parent: + { + if (n.parent()) step_push(ns, n.parent(), alloc); + + break; + } + + default: + assert(!"Unimplemented axis"); + } + } + + template void step_fill(xpath_node_set_raw& ns, const xml_attribute& a, const xml_node& p, xpath_allocator* alloc, T v) + { + const axis_t axis = T::axis; + + switch (axis) + { + case axis_ancestor: + case axis_ancestor_or_self: + { + if (axis == axis_ancestor_or_self && _test == nodetest_type_node) // reject attributes based on principal node type test + step_push(ns, a, p, alloc); + + xml_node cur = p; + + while (cur) + { + step_push(ns, cur, alloc); + + cur = cur.parent(); + } + + break; + } + + case axis_descendant_or_self: + case axis_self: + { + if (_test == nodetest_type_node) // reject attributes based on principal node type test + step_push(ns, a, p, alloc); + + break; + } + + case axis_following: + { + xml_node cur = p; + + for (;;) + { + if (cur.first_child()) + cur = cur.first_child(); + else if (cur.next_sibling()) + cur = cur.next_sibling(); + else + { + while (cur && !cur.next_sibling()) cur = cur.parent(); + cur = cur.next_sibling(); + + if (!cur) break; + } + + step_push(ns, cur, alloc); + } + + break; + } + + case axis_parent: + { + step_push(ns, p, alloc); + + break; + } + + case axis_preceding: + { + // preceding:: axis does not include attribute nodes and attribute ancestors (they are the same as parent's ancestors), so we can reuse node preceding + step_fill(ns, p, alloc, v); + break; + } + + default: + assert(!"Unimplemented axis"); + } + } + + template xpath_node_set_raw step_do(const xpath_context& c, const xpath_stack& stack, T v) + { + const axis_t axis = T::axis; + bool attributes = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_descendant_or_self || axis == axis_following || axis == axis_parent || axis == axis_preceding || axis == axis_self); + + xpath_node_set_raw ns; + ns.set_type((axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_preceding || axis == axis_preceding_sibling) ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted); + + if (_left) + { + xpath_node_set_raw s = _left->eval_node_set(c, stack); + + // self axis preserves the original order + if (axis == axis_self) ns.set_type(s.type()); + + for (const xpath_node* it = s.begin(); it != s.end(); ++it) + { + size_t size = ns.size(); + + // in general, all axes generate elements in a particular order, but there is no order guarantee if axis is applied to two nodes + if (axis != axis_self && size != 0) ns.set_type(xpath_node_set::type_unsorted); + + if (it->node()) + step_fill(ns, it->node(), stack.result, v); + else if (attributes) + step_fill(ns, it->attribute(), it->parent(), stack.result, v); + + apply_predicates(ns, size, stack); + } + } + else + { + if (c.n.node()) + step_fill(ns, c.n.node(), stack.result, v); + else if (attributes) + step_fill(ns, c.n.attribute(), c.n.parent(), stack.result, v); + + apply_predicates(ns, 0, stack); + } + + // child, attribute and self axes always generate unique set of nodes + // for other axis, if the set stayed sorted, it stayed unique because the traversal algorithms do not visit the same node twice + if (axis != axis_child && axis != axis_attribute && axis != axis_self && ns.type() == xpath_node_set::type_unsorted) + ns.remove_duplicates(); + + return ns; + } + + public: + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, const char_t* value): + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) + { + assert(type == ast_string_constant); + _data.string = value; + } + + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, double value): + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) + { + assert(type == ast_number_constant); + _data.number = value; + } + + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_variable* value): + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(0), _right(0), _next(0) + { + assert(type == ast_variable); + _data.variable = value; + } + + xpath_ast_node(ast_type_t type, xpath_value_type rettype_, xpath_ast_node* left = 0, xpath_ast_node* right = 0): + _type(static_cast(type)), _rettype(static_cast(rettype_)), _axis(0), _test(0), _left(left), _right(right), _next(0) + { + } + + xpath_ast_node(ast_type_t type, xpath_ast_node* left, axis_t axis, nodetest_t test, const char_t* contents): + _type(static_cast(type)), _rettype(xpath_type_node_set), _axis(static_cast(axis)), _test(static_cast(test)), _left(left), _right(0), _next(0) + { + _data.nodetest = contents; + } + + void set_next(xpath_ast_node* value) + { + _next = value; + } + + void set_right(xpath_ast_node* value) + { + _right = value; + } + + bool eval_boolean(const xpath_context& c, const xpath_stack& stack) + { + switch (_type) + { + case ast_op_or: + return _left->eval_boolean(c, stack) || _right->eval_boolean(c, stack); + + case ast_op_and: + return _left->eval_boolean(c, stack) && _right->eval_boolean(c, stack); + + case ast_op_equal: + return compare_eq(_left, _right, c, stack, equal_to()); + + case ast_op_not_equal: + return compare_eq(_left, _right, c, stack, not_equal_to()); + + case ast_op_less: + return compare_rel(_left, _right, c, stack, less()); + + case ast_op_greater: + return compare_rel(_right, _left, c, stack, less()); + + case ast_op_less_or_equal: + return compare_rel(_left, _right, c, stack, less_equal()); + + case ast_op_greater_or_equal: + return compare_rel(_right, _left, c, stack, less_equal()); + + case ast_func_starts_with: + { + xpath_allocator_capture cr(stack.result); + + xpath_string lr = _left->eval_string(c, stack); + xpath_string rr = _right->eval_string(c, stack); + + return starts_with(lr.c_str(), rr.c_str()); + } + + case ast_func_contains: + { + xpath_allocator_capture cr(stack.result); + + xpath_string lr = _left->eval_string(c, stack); + xpath_string rr = _right->eval_string(c, stack); + + return find_substring(lr.c_str(), rr.c_str()) != 0; + } + + case ast_func_boolean: + return _left->eval_boolean(c, stack); + + case ast_func_not: + return !_left->eval_boolean(c, stack); + + case ast_func_true: + return true; + + case ast_func_false: + return false; + + case ast_func_lang: + { + if (c.n.attribute()) return false; + + xpath_allocator_capture cr(stack.result); + + xpath_string lang = _left->eval_string(c, stack); + + for (xml_node n = c.n.node(); n; n = n.parent()) + { + xml_attribute a = n.attribute(PUGIXML_TEXT("xml:lang")); + + if (a) + { + const char_t* value = a.value(); + + // strnicmp / strncasecmp is not portable + for (const char_t* lit = lang.c_str(); *lit; ++lit) + { + if (tolower_ascii(*lit) != tolower_ascii(*value)) return false; + ++value; + } + + return *value == 0 || *value == '-'; + } + } + + return false; + } + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_boolean) + return _data.variable->get_boolean(); + + // fallthrough to type conversion + } + + default: + { + switch (_rettype) + { + case xpath_type_number: + return convert_number_to_boolean(eval_number(c, stack)); + + case xpath_type_string: + { + xpath_allocator_capture cr(stack.result); + + return !eval_string(c, stack).empty(); + } + + case xpath_type_node_set: + { + xpath_allocator_capture cr(stack.result); + + return !eval_node_set(c, stack).empty(); + } + + default: + assert(!"Wrong expression for return type boolean"); + return false; + } + } + } + } + + double eval_number(const xpath_context& c, const xpath_stack& stack) + { + switch (_type) + { + case ast_op_add: + return _left->eval_number(c, stack) + _right->eval_number(c, stack); + + case ast_op_subtract: + return _left->eval_number(c, stack) - _right->eval_number(c, stack); + + case ast_op_multiply: + return _left->eval_number(c, stack) * _right->eval_number(c, stack); + + case ast_op_divide: + return _left->eval_number(c, stack) / _right->eval_number(c, stack); + + case ast_op_mod: + return fmod(_left->eval_number(c, stack), _right->eval_number(c, stack)); + + case ast_op_negate: + return -_left->eval_number(c, stack); + + case ast_number_constant: + return _data.number; + + case ast_func_last: + return static_cast(c.size); + + case ast_func_position: + return static_cast(c.position); + + case ast_func_count: + { + xpath_allocator_capture cr(stack.result); + + return static_cast(_left->eval_node_set(c, stack).size()); + } + + case ast_func_string_length_0: + { + xpath_allocator_capture cr(stack.result); + + return static_cast(string_value(c.n, stack.result).length()); + } + + case ast_func_string_length_1: + { + xpath_allocator_capture cr(stack.result); + + return static_cast(_left->eval_string(c, stack).length()); + } + + case ast_func_number_0: + { + xpath_allocator_capture cr(stack.result); + + return convert_string_to_number(string_value(c.n, stack.result).c_str()); + } + + case ast_func_number_1: + return _left->eval_number(c, stack); + + case ast_func_sum: + { + xpath_allocator_capture cr(stack.result); + + double r = 0; + + xpath_node_set_raw ns = _left->eval_node_set(c, stack); + + for (const xpath_node* it = ns.begin(); it != ns.end(); ++it) + { + xpath_allocator_capture cri(stack.result); + + r += convert_string_to_number(string_value(*it, stack.result).c_str()); + } + + return r; + } + + case ast_func_floor: + { + double r = _left->eval_number(c, stack); + + return r == r ? floor(r) : r; + } + + case ast_func_ceiling: + { + double r = _left->eval_number(c, stack); + + return r == r ? ceil(r) : r; + } + + case ast_func_round: + return round_nearest_nzero(_left->eval_number(c, stack)); + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_number) + return _data.variable->get_number(); + + // fallthrough to type conversion + } + + default: + { + switch (_rettype) + { + case xpath_type_boolean: + return eval_boolean(c, stack) ? 1 : 0; + + case xpath_type_string: + { + xpath_allocator_capture cr(stack.result); + + return convert_string_to_number(eval_string(c, stack).c_str()); + } + + case xpath_type_node_set: + { + xpath_allocator_capture cr(stack.result); + + return convert_string_to_number(eval_string(c, stack).c_str()); + } + + default: + assert(!"Wrong expression for return type number"); + return 0; + } + + } + } + } + + xpath_string eval_string_concat(const xpath_context& c, const xpath_stack& stack) + { + assert(_type == ast_func_concat); + + xpath_allocator_capture ct(stack.temp); + + // count the string number + size_t count = 1; + for (xpath_ast_node* nc = _right; nc; nc = nc->_next) count++; + + // gather all strings + xpath_string static_buffer[4]; + xpath_string* buffer = static_buffer; + + // allocate on-heap for large concats + if (count > sizeof(static_buffer) / sizeof(static_buffer[0])) + { + buffer = static_cast(stack.temp->allocate(count * sizeof(xpath_string))); + assert(buffer); + } + + // evaluate all strings to temporary stack + xpath_stack swapped_stack = {stack.temp, stack.result}; + + buffer[0] = _left->eval_string(c, swapped_stack); + + size_t pos = 1; + for (xpath_ast_node* n = _right; n; n = n->_next, ++pos) buffer[pos] = n->eval_string(c, swapped_stack); + assert(pos == count); + + // get total length + size_t length = 0; + for (size_t i = 0; i < count; ++i) length += buffer[i].length(); + + // create final string + char_t* result = static_cast(stack.result->allocate((length + 1) * sizeof(char_t))); + assert(result); + + char_t* ri = result; + + for (size_t j = 0; j < count; ++j) + for (const char_t* bi = buffer[j].c_str(); *bi; ++bi) + *ri++ = *bi; + + *ri = 0; + + return xpath_string(result, true); + } + + xpath_string eval_string(const xpath_context& c, const xpath_stack& stack) + { + switch (_type) + { + case ast_string_constant: + return xpath_string_const(_data.string); + + case ast_func_local_name_0: + { + xpath_node na = c.n; + + return xpath_string_const(local_name(na)); + } + + case ast_func_local_name_1: + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ns = _left->eval_node_set(c, stack); + xpath_node na = ns.first(); + + return xpath_string_const(local_name(na)); + } + + case ast_func_name_0: + { + xpath_node na = c.n; + + return xpath_string_const(qualified_name(na)); + } + + case ast_func_name_1: + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ns = _left->eval_node_set(c, stack); + xpath_node na = ns.first(); + + return xpath_string_const(qualified_name(na)); + } + + case ast_func_namespace_uri_0: + { + xpath_node na = c.n; + + return xpath_string_const(namespace_uri(na)); + } + + case ast_func_namespace_uri_1: + { + xpath_allocator_capture cr(stack.result); + + xpath_node_set_raw ns = _left->eval_node_set(c, stack); + xpath_node na = ns.first(); + + return xpath_string_const(namespace_uri(na)); + } + + case ast_func_string_0: + return string_value(c.n, stack.result); + + case ast_func_string_1: + return _left->eval_string(c, stack); + + case ast_func_concat: + return eval_string_concat(c, stack); + + case ast_func_substring_before: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + xpath_string p = _right->eval_string(c, swapped_stack); + + const char_t* pos = find_substring(s.c_str(), p.c_str()); + + return pos ? xpath_string(s.c_str(), pos, stack.result) : xpath_string(); + } + + case ast_func_substring_after: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + xpath_string p = _right->eval_string(c, swapped_stack); + + const char_t* pos = find_substring(s.c_str(), p.c_str()); + if (!pos) return xpath_string(); + + const char_t* result = pos + p.length(); + + return s.uses_heap() ? xpath_string(result, stack.result) : xpath_string_const(result); + } + + case ast_func_substring_2: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + size_t s_length = s.length(); + + double first = round_nearest(_right->eval_number(c, stack)); + + if (is_nan(first)) return xpath_string(); // NaN + else if (first >= s_length + 1) return xpath_string(); + + size_t pos = first < 1 ? 1 : static_cast(first); + assert(1 <= pos && pos <= s_length + 1); + + const char_t* rbegin = s.c_str() + (pos - 1); + + return s.uses_heap() ? xpath_string(rbegin, stack.result) : xpath_string_const(rbegin); + } + + case ast_func_substring_3: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, swapped_stack); + size_t s_length = s.length(); + + double first = round_nearest(_right->eval_number(c, stack)); + double last = first + round_nearest(_right->_next->eval_number(c, stack)); + + if (is_nan(first) || is_nan(last)) return xpath_string(); + else if (first >= s_length + 1) return xpath_string(); + else if (first >= last) return xpath_string(); + else if (last < 1) return xpath_string(); + + size_t pos = first < 1 ? 1 : static_cast(first); + size_t end = last >= s_length + 1 ? s_length + 1 : static_cast(last); + + assert(1 <= pos && pos <= end && end <= s_length + 1); + const char_t* rbegin = s.c_str() + (pos - 1); + const char_t* rend = s.c_str() + (end - 1); + + return (end == s_length + 1 && !s.uses_heap()) ? xpath_string_const(rbegin) : xpath_string(rbegin, rend, stack.result); + } + + case ast_func_normalize_space_0: + { + xpath_string s = string_value(c.n, stack.result); + + normalize_space(s.data(stack.result)); + + return s; + } + + case ast_func_normalize_space_1: + { + xpath_string s = _left->eval_string(c, stack); + + normalize_space(s.data(stack.result)); + + return s; + } + + case ast_func_translate: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_string s = _left->eval_string(c, stack); + xpath_string from = _right->eval_string(c, swapped_stack); + xpath_string to = _right->_next->eval_string(c, swapped_stack); + + translate(s.data(stack.result), from.c_str(), to.c_str()); + + return s; + } + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_string) + return xpath_string_const(_data.variable->get_string()); + + // fallthrough to type conversion + } + + default: + { + switch (_rettype) + { + case xpath_type_boolean: + return xpath_string_const(eval_boolean(c, stack) ? PUGIXML_TEXT("true") : PUGIXML_TEXT("false")); + + case xpath_type_number: + return convert_number_to_string(eval_number(c, stack), stack.result); + + case xpath_type_node_set: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_node_set_raw ns = eval_node_set(c, swapped_stack); + return ns.empty() ? xpath_string() : string_value(ns.first(), stack.result); + } + + default: + assert(!"Wrong expression for return type string"); + return xpath_string(); + } + } + } + } + + xpath_node_set_raw eval_node_set(const xpath_context& c, const xpath_stack& stack) + { + switch (_type) + { + case ast_op_union: + { + xpath_allocator_capture cr(stack.temp); + + xpath_stack swapped_stack = {stack.temp, stack.result}; + + xpath_node_set_raw ls = _left->eval_node_set(c, swapped_stack); + xpath_node_set_raw rs = _right->eval_node_set(c, stack); + + // we can optimize merging two sorted sets, but this is a very rare operation, so don't bother + rs.set_type(xpath_node_set::type_unsorted); + + rs.append(ls.begin(), ls.end(), stack.result); + rs.remove_duplicates(); + + return rs; + } + + case ast_filter: + case ast_filter_posinv: + { + xpath_node_set_raw set = _left->eval_node_set(c, stack); + + // either expression is a number or it contains position() call; sort by document order + if (_type == ast_filter) set.sort_do(); + + apply_predicate(set, 0, _right, stack); + + return set; + } + + case ast_func_id: + return xpath_node_set_raw(); + + case ast_step: + { + switch (_axis) + { + case axis_ancestor: + return step_do(c, stack, axis_to_type()); + + case axis_ancestor_or_self: + return step_do(c, stack, axis_to_type()); + + case axis_attribute: + return step_do(c, stack, axis_to_type()); + + case axis_child: + return step_do(c, stack, axis_to_type()); + + case axis_descendant: + return step_do(c, stack, axis_to_type()); + + case axis_descendant_or_self: + return step_do(c, stack, axis_to_type()); + + case axis_following: + return step_do(c, stack, axis_to_type()); + + case axis_following_sibling: + return step_do(c, stack, axis_to_type()); + + case axis_namespace: + // namespaced axis is not supported + return xpath_node_set_raw(); + + case axis_parent: + return step_do(c, stack, axis_to_type()); + + case axis_preceding: + return step_do(c, stack, axis_to_type()); + + case axis_preceding_sibling: + return step_do(c, stack, axis_to_type()); + + case axis_self: + return step_do(c, stack, axis_to_type()); + + default: + assert(!"Unknown axis"); + return xpath_node_set_raw(); + } + } + + case ast_step_root: + { + assert(!_right); // root step can't have any predicates + + xpath_node_set_raw ns; + + ns.set_type(xpath_node_set::type_sorted); + + if (c.n.node()) ns.push_back(c.n.node().root(), stack.result); + else if (c.n.attribute()) ns.push_back(c.n.parent().root(), stack.result); + + return ns; + } + + case ast_variable: + { + assert(_rettype == _data.variable->type()); + + if (_rettype == xpath_type_node_set) + { + const xpath_node_set& s = _data.variable->get_node_set(); + + xpath_node_set_raw ns; + + ns.set_type(s.type()); + ns.append(s.begin(), s.end(), stack.result); + + return ns; + } + + // fallthrough to type conversion + } + + default: + assert(!"Wrong expression for return type node set"); + return xpath_node_set_raw(); + } + } + + bool is_posinv() + { + switch (_type) + { + case ast_func_position: + return false; + + case ast_string_constant: + case ast_number_constant: + case ast_variable: + return true; + + case ast_step: + case ast_step_root: + return true; + + case ast_predicate: + case ast_filter: + case ast_filter_posinv: + return true; + + default: + if (_left && !_left->is_posinv()) return false; + + for (xpath_ast_node* n = _right; n; n = n->_next) + if (!n->is_posinv()) return false; + + return true; + } + } + + xpath_value_type rettype() const + { + return static_cast(_rettype); + } + }; + + struct xpath_parser + { + xpath_allocator* _alloc; + xpath_lexer _lexer; + + const char_t* _query; + xpath_variable_set* _variables; + + xpath_parse_result* _result; + + char_t _scratch[32]; + + #ifdef PUGIXML_NO_EXCEPTIONS + jmp_buf _error_handler; + #endif + + void throw_error(const char* message) + { + _result->error = message; + _result->offset = _lexer.current_pos() - _query; + + #ifdef PUGIXML_NO_EXCEPTIONS + longjmp(_error_handler, 1); + #else + throw xpath_exception(*_result); + #endif + } + + void throw_error_oom() + { + #ifdef PUGIXML_NO_EXCEPTIONS + throw_error("Out of memory"); + #else + throw std::bad_alloc(); + #endif + } + + void* alloc_node() + { + void* result = _alloc->allocate_nothrow(sizeof(xpath_ast_node)); + + if (!result) throw_error_oom(); + + return result; + } + + const char_t* alloc_string(const xpath_lexer_string& value) + { + if (value.begin) + { + size_t length = static_cast(value.end - value.begin); + + char_t* c = static_cast(_alloc->allocate_nothrow((length + 1) * sizeof(char_t))); + if (!c) throw_error_oom(); + assert(c); // workaround for clang static analysis + + memcpy(c, value.begin, length * sizeof(char_t)); + c[length] = 0; + + return c; + } + else return 0; + } + + xpath_ast_node* parse_function_helper(ast_type_t type0, ast_type_t type1, size_t argc, xpath_ast_node* args[2]) + { + assert(argc <= 1); + + if (argc == 1 && args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); + + return new (alloc_node()) xpath_ast_node(argc == 0 ? type0 : type1, xpath_type_string, args[0]); + } + + xpath_ast_node* parse_function(const xpath_lexer_string& name, size_t argc, xpath_ast_node* args[2]) + { + switch (name.begin[0]) + { + case 'b': + if (name == PUGIXML_TEXT("boolean") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_boolean, xpath_type_boolean, args[0]); + + break; + + case 'c': + if (name == PUGIXML_TEXT("count") && argc == 1) + { + if (args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); + return new (alloc_node()) xpath_ast_node(ast_func_count, xpath_type_number, args[0]); + } + else if (name == PUGIXML_TEXT("contains") && argc == 2) + return new (alloc_node()) xpath_ast_node(ast_func_contains, xpath_type_boolean, args[0], args[1]); + else if (name == PUGIXML_TEXT("concat") && argc >= 2) + return new (alloc_node()) xpath_ast_node(ast_func_concat, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("ceiling") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_ceiling, xpath_type_number, args[0]); + + break; + + case 'f': + if (name == PUGIXML_TEXT("false") && argc == 0) + return new (alloc_node()) xpath_ast_node(ast_func_false, xpath_type_boolean); + else if (name == PUGIXML_TEXT("floor") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_floor, xpath_type_number, args[0]); + + break; + + case 'i': + if (name == PUGIXML_TEXT("id") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_id, xpath_type_node_set, args[0]); + + break; + + case 'l': + if (name == PUGIXML_TEXT("last") && argc == 0) + return new (alloc_node()) xpath_ast_node(ast_func_last, xpath_type_number); + else if (name == PUGIXML_TEXT("lang") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_lang, xpath_type_boolean, args[0]); + else if (name == PUGIXML_TEXT("local-name") && argc <= 1) + return parse_function_helper(ast_func_local_name_0, ast_func_local_name_1, argc, args); + + break; + + case 'n': + if (name == PUGIXML_TEXT("name") && argc <= 1) + return parse_function_helper(ast_func_name_0, ast_func_name_1, argc, args); + else if (name == PUGIXML_TEXT("namespace-uri") && argc <= 1) + return parse_function_helper(ast_func_namespace_uri_0, ast_func_namespace_uri_1, argc, args); + else if (name == PUGIXML_TEXT("normalize-space") && argc <= 1) + return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_normalize_space_0 : ast_func_normalize_space_1, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("not") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_not, xpath_type_boolean, args[0]); + else if (name == PUGIXML_TEXT("number") && argc <= 1) + return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_number_0 : ast_func_number_1, xpath_type_number, args[0]); + + break; + + case 'p': + if (name == PUGIXML_TEXT("position") && argc == 0) + return new (alloc_node()) xpath_ast_node(ast_func_position, xpath_type_number); + + break; + + case 'r': + if (name == PUGIXML_TEXT("round") && argc == 1) + return new (alloc_node()) xpath_ast_node(ast_func_round, xpath_type_number, args[0]); + + break; + + case 's': + if (name == PUGIXML_TEXT("string") && argc <= 1) + return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_string_0 : ast_func_string_1, xpath_type_string, args[0]); + else if (name == PUGIXML_TEXT("string-length") && argc <= 1) + return new (alloc_node()) xpath_ast_node(argc == 0 ? ast_func_string_length_0 : ast_func_string_length_1, xpath_type_number, args[0]); + else if (name == PUGIXML_TEXT("starts-with") && argc == 2) + return new (alloc_node()) xpath_ast_node(ast_func_starts_with, xpath_type_boolean, args[0], args[1]); + else if (name == PUGIXML_TEXT("substring-before") && argc == 2) + return new (alloc_node()) xpath_ast_node(ast_func_substring_before, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("substring-after") && argc == 2) + return new (alloc_node()) xpath_ast_node(ast_func_substring_after, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("substring") && (argc == 2 || argc == 3)) + return new (alloc_node()) xpath_ast_node(argc == 2 ? ast_func_substring_2 : ast_func_substring_3, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("sum") && argc == 1) + { + if (args[0]->rettype() != xpath_type_node_set) throw_error("Function has to be applied to node set"); + return new (alloc_node()) xpath_ast_node(ast_func_sum, xpath_type_number, args[0]); + } + + break; + + case 't': + if (name == PUGIXML_TEXT("translate") && argc == 3) + return new (alloc_node()) xpath_ast_node(ast_func_translate, xpath_type_string, args[0], args[1]); + else if (name == PUGIXML_TEXT("true") && argc == 0) + return new (alloc_node()) xpath_ast_node(ast_func_true, xpath_type_boolean); + + break; + + default: + break; + } + + throw_error("Unrecognized function or wrong parameter count"); + + return 0; + } + + axis_t parse_axis_name(const xpath_lexer_string& name, bool& specified) + { + specified = true; + + switch (name.begin[0]) + { + case 'a': + if (name == PUGIXML_TEXT("ancestor")) + return axis_ancestor; + else if (name == PUGIXML_TEXT("ancestor-or-self")) + return axis_ancestor_or_self; + else if (name == PUGIXML_TEXT("attribute")) + return axis_attribute; + + break; + + case 'c': + if (name == PUGIXML_TEXT("child")) + return axis_child; + + break; + + case 'd': + if (name == PUGIXML_TEXT("descendant")) + return axis_descendant; + else if (name == PUGIXML_TEXT("descendant-or-self")) + return axis_descendant_or_self; + + break; + + case 'f': + if (name == PUGIXML_TEXT("following")) + return axis_following; + else if (name == PUGIXML_TEXT("following-sibling")) + return axis_following_sibling; + + break; + + case 'n': + if (name == PUGIXML_TEXT("namespace")) + return axis_namespace; + + break; + + case 'p': + if (name == PUGIXML_TEXT("parent")) + return axis_parent; + else if (name == PUGIXML_TEXT("preceding")) + return axis_preceding; + else if (name == PUGIXML_TEXT("preceding-sibling")) + return axis_preceding_sibling; + + break; + + case 's': + if (name == PUGIXML_TEXT("self")) + return axis_self; + + break; + + default: + break; + } + + specified = false; + return axis_child; + } + + nodetest_t parse_node_test_type(const xpath_lexer_string& name) + { + switch (name.begin[0]) + { + case 'c': + if (name == PUGIXML_TEXT("comment")) + return nodetest_type_comment; + + break; + + case 'n': + if (name == PUGIXML_TEXT("node")) + return nodetest_type_node; + + break; + + case 'p': + if (name == PUGIXML_TEXT("processing-instruction")) + return nodetest_type_pi; + + break; + + case 't': + if (name == PUGIXML_TEXT("text")) + return nodetest_type_text; + + break; + + default: + break; + } + + return nodetest_none; + } + + // PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall + xpath_ast_node* parse_primary_expression() + { + switch (_lexer.current()) + { + case lex_var_ref: + { + xpath_lexer_string name = _lexer.contents(); + + if (!_variables) + throw_error("Unknown variable: variable set is not provided"); + + xpath_variable* var = get_variable_scratch(_scratch, _variables, name.begin, name.end); + + if (!var) + throw_error("Unknown variable: variable set does not contain the given name"); + + _lexer.next(); + + return new (alloc_node()) xpath_ast_node(ast_variable, var->type(), var); + } + + case lex_open_brace: + { + _lexer.next(); + + xpath_ast_node* n = parse_expression(); + + if (_lexer.current() != lex_close_brace) + throw_error("Unmatched braces"); + + _lexer.next(); + + return n; + } + + case lex_quoted_string: + { + const char_t* value = alloc_string(_lexer.contents()); + + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_string_constant, xpath_type_string, value); + _lexer.next(); + + return n; + } + + case lex_number: + { + double value = 0; + + if (!convert_string_to_number_scratch(_scratch, _lexer.contents().begin, _lexer.contents().end, &value)) + throw_error_oom(); + + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_number_constant, xpath_type_number, value); + _lexer.next(); + + return n; + } + + case lex_string: + { + xpath_ast_node* args[2] = {0}; + size_t argc = 0; + + xpath_lexer_string function = _lexer.contents(); + _lexer.next(); + + xpath_ast_node* last_arg = 0; + + if (_lexer.current() != lex_open_brace) + throw_error("Unrecognized function call"); + _lexer.next(); + + if (_lexer.current() != lex_close_brace) + args[argc++] = parse_expression(); + + while (_lexer.current() != lex_close_brace) + { + if (_lexer.current() != lex_comma) + throw_error("No comma between function arguments"); + _lexer.next(); + + xpath_ast_node* n = parse_expression(); + + if (argc < 2) args[argc] = n; + else last_arg->set_next(n); + + argc++; + last_arg = n; + } + + _lexer.next(); + + return parse_function(function, argc, args); + } + + default: + throw_error("Unrecognizable primary expression"); + + return 0; + } + } + + // FilterExpr ::= PrimaryExpr | FilterExpr Predicate + // Predicate ::= '[' PredicateExpr ']' + // PredicateExpr ::= Expr + xpath_ast_node* parse_filter_expression() + { + xpath_ast_node* n = parse_primary_expression(); + + while (_lexer.current() == lex_open_square_brace) + { + _lexer.next(); + + xpath_ast_node* expr = parse_expression(); + + if (n->rettype() != xpath_type_node_set) throw_error("Predicate has to be applied to node set"); + + bool posinv = expr->rettype() != xpath_type_number && expr->is_posinv(); + + n = new (alloc_node()) xpath_ast_node(posinv ? ast_filter_posinv : ast_filter, xpath_type_node_set, n, expr); + + if (_lexer.current() != lex_close_square_brace) + throw_error("Unmatched square brace"); + + _lexer.next(); + } + + return n; + } + + // Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep + // AxisSpecifier ::= AxisName '::' | '@'? + // NodeTest ::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')' + // NameTest ::= '*' | NCName ':' '*' | QName + // AbbreviatedStep ::= '.' | '..' + xpath_ast_node* parse_step(xpath_ast_node* set) + { + if (set && set->rettype() != xpath_type_node_set) + throw_error("Step has to be applied to node set"); + + bool axis_specified = false; + axis_t axis = axis_child; // implied child axis + + if (_lexer.current() == lex_axis_attribute) + { + axis = axis_attribute; + axis_specified = true; + + _lexer.next(); + } + else if (_lexer.current() == lex_dot) + { + _lexer.next(); + + return new (alloc_node()) xpath_ast_node(ast_step, set, axis_self, nodetest_type_node, 0); + } + else if (_lexer.current() == lex_double_dot) + { + _lexer.next(); + + return new (alloc_node()) xpath_ast_node(ast_step, set, axis_parent, nodetest_type_node, 0); + } + + nodetest_t nt_type = nodetest_none; + xpath_lexer_string nt_name; + + if (_lexer.current() == lex_string) + { + // node name test + nt_name = _lexer.contents(); + _lexer.next(); + + // was it an axis name? + if (_lexer.current() == lex_double_colon) + { + // parse axis name + if (axis_specified) throw_error("Two axis specifiers in one step"); + + axis = parse_axis_name(nt_name, axis_specified); + + if (!axis_specified) throw_error("Unknown axis"); + + // read actual node test + _lexer.next(); + + if (_lexer.current() == lex_multiply) + { + nt_type = nodetest_all; + nt_name = xpath_lexer_string(); + _lexer.next(); + } + else if (_lexer.current() == lex_string) + { + nt_name = _lexer.contents(); + _lexer.next(); + } + else throw_error("Unrecognized node test"); + } + + if (nt_type == nodetest_none) + { + // node type test or processing-instruction + if (_lexer.current() == lex_open_brace) + { + _lexer.next(); + + if (_lexer.current() == lex_close_brace) + { + _lexer.next(); + + nt_type = parse_node_test_type(nt_name); + + if (nt_type == nodetest_none) throw_error("Unrecognized node type"); + + nt_name = xpath_lexer_string(); + } + else if (nt_name == PUGIXML_TEXT("processing-instruction")) + { + if (_lexer.current() != lex_quoted_string) + throw_error("Only literals are allowed as arguments to processing-instruction()"); + + nt_type = nodetest_pi; + nt_name = _lexer.contents(); + _lexer.next(); + + if (_lexer.current() != lex_close_brace) + throw_error("Unmatched brace near processing-instruction()"); + _lexer.next(); + } + else + throw_error("Unmatched brace near node type test"); + + } + // QName or NCName:* + else + { + if (nt_name.end - nt_name.begin > 2 && nt_name.end[-2] == ':' && nt_name.end[-1] == '*') // NCName:* + { + nt_name.end--; // erase * + + nt_type = nodetest_all_in_namespace; + } + else nt_type = nodetest_name; + } + } + } + else if (_lexer.current() == lex_multiply) + { + nt_type = nodetest_all; + _lexer.next(); + } + else throw_error("Unrecognized node test"); + + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step, set, axis, nt_type, alloc_string(nt_name)); + + xpath_ast_node* last = 0; + + while (_lexer.current() == lex_open_square_brace) + { + _lexer.next(); + + xpath_ast_node* expr = parse_expression(); + + xpath_ast_node* pred = new (alloc_node()) xpath_ast_node(ast_predicate, xpath_type_node_set, expr); + + if (_lexer.current() != lex_close_square_brace) + throw_error("Unmatched square brace"); + _lexer.next(); + + if (last) last->set_next(pred); + else n->set_right(pred); + + last = pred; + } + + return n; + } + + // RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | RelativeLocationPath '//' Step + xpath_ast_node* parse_relative_location_path(xpath_ast_node* set) + { + xpath_ast_node* n = parse_step(set); + + while (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) + { + lexeme_t l = _lexer.current(); + _lexer.next(); + + if (l == lex_double_slash) + n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); + + n = parse_step(n); + } + + return n; + } + + // LocationPath ::= RelativeLocationPath | AbsoluteLocationPath + // AbsoluteLocationPath ::= '/' RelativeLocationPath? | '//' RelativeLocationPath + xpath_ast_node* parse_location_path() + { + if (_lexer.current() == lex_slash) + { + _lexer.next(); + + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set); + + // relative location path can start from axis_attribute, dot, double_dot, multiply and string lexemes; any other lexeme means standalone root path + lexeme_t l = _lexer.current(); + + if (l == lex_string || l == lex_axis_attribute || l == lex_dot || l == lex_double_dot || l == lex_multiply) + return parse_relative_location_path(n); + else + return n; + } + else if (_lexer.current() == lex_double_slash) + { + _lexer.next(); + + xpath_ast_node* n = new (alloc_node()) xpath_ast_node(ast_step_root, xpath_type_node_set); + n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); + + return parse_relative_location_path(n); + } + + // else clause moved outside of if because of bogus warning 'control may reach end of non-void function being inlined' in gcc 4.0.1 + return parse_relative_location_path(0); + } + + // PathExpr ::= LocationPath + // | FilterExpr + // | FilterExpr '/' RelativeLocationPath + // | FilterExpr '//' RelativeLocationPath + // UnionExpr ::= PathExpr | UnionExpr '|' PathExpr + // UnaryExpr ::= UnionExpr | '-' UnaryExpr + xpath_ast_node* parse_path_or_unary_expression() + { + // Clarification. + // PathExpr begins with either LocationPath or FilterExpr. + // FilterExpr begins with PrimaryExpr + // PrimaryExpr begins with '$' in case of it being a variable reference, + // '(' in case of it being an expression, string literal, number constant or + // function call. + + if (_lexer.current() == lex_var_ref || _lexer.current() == lex_open_brace || + _lexer.current() == lex_quoted_string || _lexer.current() == lex_number || + _lexer.current() == lex_string) + { + if (_lexer.current() == lex_string) + { + // This is either a function call, or not - if not, we shall proceed with location path + const char_t* state = _lexer.state(); + + while (PUGI__IS_CHARTYPE(*state, ct_space)) ++state; + + if (*state != '(') return parse_location_path(); + + // This looks like a function call; however this still can be a node-test. Check it. + if (parse_node_test_type(_lexer.contents()) != nodetest_none) return parse_location_path(); + } + + xpath_ast_node* n = parse_filter_expression(); + + if (_lexer.current() == lex_slash || _lexer.current() == lex_double_slash) + { + lexeme_t l = _lexer.current(); + _lexer.next(); + + if (l == lex_double_slash) + { + if (n->rettype() != xpath_type_node_set) throw_error("Step has to be applied to node set"); + + n = new (alloc_node()) xpath_ast_node(ast_step, n, axis_descendant_or_self, nodetest_type_node, 0); + } + + // select from location path + return parse_relative_location_path(n); + } + + return n; + } + else if (_lexer.current() == lex_minus) + { + _lexer.next(); + + // precedence 7+ - only parses union expressions + xpath_ast_node* expr = parse_expression_rec(parse_path_or_unary_expression(), 7); + + return new (alloc_node()) xpath_ast_node(ast_op_negate, xpath_type_number, expr); + } + else + return parse_location_path(); + } + + struct binary_op_t + { + ast_type_t asttype; + xpath_value_type rettype; + int precedence; + + binary_op_t(): asttype(ast_unknown), rettype(xpath_type_none), precedence(0) + { + } + + binary_op_t(ast_type_t asttype_, xpath_value_type rettype_, int precedence_): asttype(asttype_), rettype(rettype_), precedence(precedence_) + { + } + + static binary_op_t parse(xpath_lexer& lexer) + { + switch (lexer.current()) + { + case lex_string: + if (lexer.contents() == PUGIXML_TEXT("or")) + return binary_op_t(ast_op_or, xpath_type_boolean, 1); + else if (lexer.contents() == PUGIXML_TEXT("and")) + return binary_op_t(ast_op_and, xpath_type_boolean, 2); + else if (lexer.contents() == PUGIXML_TEXT("div")) + return binary_op_t(ast_op_divide, xpath_type_number, 6); + else if (lexer.contents() == PUGIXML_TEXT("mod")) + return binary_op_t(ast_op_mod, xpath_type_number, 6); + else + return binary_op_t(); + + case lex_equal: + return binary_op_t(ast_op_equal, xpath_type_boolean, 3); + + case lex_not_equal: + return binary_op_t(ast_op_not_equal, xpath_type_boolean, 3); + + case lex_less: + return binary_op_t(ast_op_less, xpath_type_boolean, 4); + + case lex_greater: + return binary_op_t(ast_op_greater, xpath_type_boolean, 4); + + case lex_less_or_equal: + return binary_op_t(ast_op_less_or_equal, xpath_type_boolean, 4); + + case lex_greater_or_equal: + return binary_op_t(ast_op_greater_or_equal, xpath_type_boolean, 4); + + case lex_plus: + return binary_op_t(ast_op_add, xpath_type_number, 5); + + case lex_minus: + return binary_op_t(ast_op_subtract, xpath_type_number, 5); + + case lex_multiply: + return binary_op_t(ast_op_multiply, xpath_type_number, 6); + + case lex_union: + return binary_op_t(ast_op_union, xpath_type_node_set, 7); + + default: + return binary_op_t(); + } + } + }; + + xpath_ast_node* parse_expression_rec(xpath_ast_node* lhs, int limit) + { + binary_op_t op = binary_op_t::parse(_lexer); + + while (op.asttype != ast_unknown && op.precedence >= limit) + { + _lexer.next(); + + xpath_ast_node* rhs = parse_path_or_unary_expression(); + + binary_op_t nextop = binary_op_t::parse(_lexer); + + while (nextop.asttype != ast_unknown && nextop.precedence > op.precedence) + { + rhs = parse_expression_rec(rhs, nextop.precedence); + + nextop = binary_op_t::parse(_lexer); + } + + if (op.asttype == ast_op_union && (lhs->rettype() != xpath_type_node_set || rhs->rettype() != xpath_type_node_set)) + throw_error("Union operator has to be applied to node sets"); + + lhs = new (alloc_node()) xpath_ast_node(op.asttype, op.rettype, lhs, rhs); + + op = binary_op_t::parse(_lexer); + } + + return lhs; + } + + // Expr ::= OrExpr + // OrExpr ::= AndExpr | OrExpr 'or' AndExpr + // AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr + // EqualityExpr ::= RelationalExpr + // | EqualityExpr '=' RelationalExpr + // | EqualityExpr '!=' RelationalExpr + // RelationalExpr ::= AdditiveExpr + // | RelationalExpr '<' AdditiveExpr + // | RelationalExpr '>' AdditiveExpr + // | RelationalExpr '<=' AdditiveExpr + // | RelationalExpr '>=' AdditiveExpr + // AdditiveExpr ::= MultiplicativeExpr + // | AdditiveExpr '+' MultiplicativeExpr + // | AdditiveExpr '-' MultiplicativeExpr + // MultiplicativeExpr ::= UnaryExpr + // | MultiplicativeExpr '*' UnaryExpr + // | MultiplicativeExpr 'div' UnaryExpr + // | MultiplicativeExpr 'mod' UnaryExpr + xpath_ast_node* parse_expression() + { + return parse_expression_rec(parse_path_or_unary_expression(), 0); + } + + xpath_parser(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result): _alloc(alloc), _lexer(query), _query(query), _variables(variables), _result(result) + { + } + + xpath_ast_node* parse() + { + xpath_ast_node* result = parse_expression(); + + if (_lexer.current() != lex_eof) + { + // there are still unparsed tokens left, error + throw_error("Incorrect query"); + } + + return result; + } + + static xpath_ast_node* parse(const char_t* query, xpath_variable_set* variables, xpath_allocator* alloc, xpath_parse_result* result) + { + xpath_parser parser(query, variables, alloc, result); + + #ifdef PUGIXML_NO_EXCEPTIONS + int error = setjmp(parser._error_handler); + + return (error == 0) ? parser.parse() : 0; + #else + return parser.parse(); + #endif + } + }; + + struct xpath_query_impl + { + static xpath_query_impl* create() + { + void* memory = xml_memory::allocate(sizeof(xpath_query_impl)); + + return new (memory) xpath_query_impl(); + } + + static void destroy(void* ptr) + { + if (!ptr) return; + + // free all allocated pages + static_cast(ptr)->alloc.release(); + + // free allocator memory (with the first page) + xml_memory::deallocate(ptr); + } + + xpath_query_impl(): root(0), alloc(&block) + { + block.next = 0; + } + + xpath_ast_node* root; + xpath_allocator alloc; + xpath_memory_block block; + }; + + PUGI__FN xpath_string evaluate_string_impl(xpath_query_impl* impl, const xpath_node& n, xpath_stack_data& sd) + { + if (!impl) return xpath_string(); + + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) return xpath_string(); + #endif + + xpath_context c(n, 1, 1); + + return impl->root->eval_string(c, sd.stack); + } +PUGI__NS_END + +namespace pugi +{ +#ifndef PUGIXML_NO_EXCEPTIONS + PUGI__FN xpath_exception::xpath_exception(const xpath_parse_result& result_): _result(result_) + { + assert(_result.error); + } + + PUGI__FN const char* xpath_exception::what() const throw() + { + return _result.error; + } + + PUGI__FN const xpath_parse_result& xpath_exception::result() const + { + return _result; + } +#endif + + PUGI__FN xpath_node::xpath_node() + { + } + + PUGI__FN xpath_node::xpath_node(const xml_node& node_): _node(node_) + { + } + + PUGI__FN xpath_node::xpath_node(const xml_attribute& attribute_, const xml_node& parent_): _node(attribute_ ? parent_ : xml_node()), _attribute(attribute_) + { + } + + PUGI__FN xml_node xpath_node::node() const + { + return _attribute ? xml_node() : _node; + } + + PUGI__FN xml_attribute xpath_node::attribute() const + { + return _attribute; + } + + PUGI__FN xml_node xpath_node::parent() const + { + return _attribute ? _node : _node.parent(); + } + + PUGI__FN static void unspecified_bool_xpath_node(xpath_node***) + { + } + + PUGI__FN xpath_node::operator xpath_node::unspecified_bool_type() const + { + return (_node || _attribute) ? unspecified_bool_xpath_node : 0; + } + + PUGI__FN bool xpath_node::operator!() const + { + return !(_node || _attribute); + } + + PUGI__FN bool xpath_node::operator==(const xpath_node& n) const + { + return _node == n._node && _attribute == n._attribute; + } + + PUGI__FN bool xpath_node::operator!=(const xpath_node& n) const + { + return _node != n._node || _attribute != n._attribute; + } + +#ifdef __BORLANDC__ + PUGI__FN bool operator&&(const xpath_node& lhs, bool rhs) + { + return (bool)lhs && rhs; + } + + PUGI__FN bool operator||(const xpath_node& lhs, bool rhs) + { + return (bool)lhs || rhs; + } +#endif + + PUGI__FN void xpath_node_set::_assign(const_iterator begin_, const_iterator end_) + { + assert(begin_ <= end_); + + size_t size_ = static_cast(end_ - begin_); + + if (size_ <= 1) + { + // deallocate old buffer + if (_begin != &_storage) impl::xml_memory::deallocate(_begin); + + // use internal buffer + if (begin_ != end_) _storage = *begin_; + + _begin = &_storage; + _end = &_storage + size_; + } + else + { + // make heap copy + xpath_node* storage = static_cast(impl::xml_memory::allocate(size_ * sizeof(xpath_node))); + + if (!storage) + { + #ifdef PUGIXML_NO_EXCEPTIONS + return; + #else + throw std::bad_alloc(); + #endif + } + + memcpy(storage, begin_, size_ * sizeof(xpath_node)); + + // deallocate old buffer + if (_begin != &_storage) impl::xml_memory::deallocate(_begin); + + // finalize + _begin = storage; + _end = storage + size_; + } + } + + PUGI__FN xpath_node_set::xpath_node_set(): _type(type_unsorted), _begin(&_storage), _end(&_storage) + { + } + + PUGI__FN xpath_node_set::xpath_node_set(const_iterator begin_, const_iterator end_, type_t type_): _type(type_), _begin(&_storage), _end(&_storage) + { + _assign(begin_, end_); + } + + PUGI__FN xpath_node_set::~xpath_node_set() + { + if (_begin != &_storage) impl::xml_memory::deallocate(_begin); + } + + PUGI__FN xpath_node_set::xpath_node_set(const xpath_node_set& ns): _type(ns._type), _begin(&_storage), _end(&_storage) + { + _assign(ns._begin, ns._end); + } + + PUGI__FN xpath_node_set& xpath_node_set::operator=(const xpath_node_set& ns) + { + if (this == &ns) return *this; + + _type = ns._type; + _assign(ns._begin, ns._end); + + return *this; + } + + PUGI__FN xpath_node_set::type_t xpath_node_set::type() const + { + return _type; + } + + PUGI__FN size_t xpath_node_set::size() const + { + return _end - _begin; + } + + PUGI__FN bool xpath_node_set::empty() const + { + return _begin == _end; + } + + PUGI__FN const xpath_node& xpath_node_set::operator[](size_t index) const + { + assert(index < size()); + return _begin[index]; + } + + PUGI__FN xpath_node_set::const_iterator xpath_node_set::begin() const + { + return _begin; + } + + PUGI__FN xpath_node_set::const_iterator xpath_node_set::end() const + { + return _end; + } + + PUGI__FN void xpath_node_set::sort(bool reverse) + { + _type = impl::xpath_sort(_begin, _end, _type, reverse); + } + + PUGI__FN xpath_node xpath_node_set::first() const + { + return impl::xpath_first(_begin, _end, _type); + } + + PUGI__FN xpath_parse_result::xpath_parse_result(): error("Internal error"), offset(0) + { + } + + PUGI__FN xpath_parse_result::operator bool() const + { + return error == 0; + } + + PUGI__FN const char* xpath_parse_result::description() const + { + return error ? error : "No error"; + } + + PUGI__FN xpath_variable::xpath_variable(): _type(xpath_type_none), _next(0) + { + } + + PUGI__FN const char_t* xpath_variable::name() const + { + switch (_type) + { + case xpath_type_node_set: + return static_cast(this)->name; + + case xpath_type_number: + return static_cast(this)->name; + + case xpath_type_string: + return static_cast(this)->name; + + case xpath_type_boolean: + return static_cast(this)->name; + + default: + assert(!"Invalid variable type"); + return 0; + } + } + + PUGI__FN xpath_value_type xpath_variable::type() const + { + return _type; + } + + PUGI__FN bool xpath_variable::get_boolean() const + { + return (_type == xpath_type_boolean) ? static_cast(this)->value : false; + } + + PUGI__FN double xpath_variable::get_number() const + { + return (_type == xpath_type_number) ? static_cast(this)->value : impl::gen_nan(); + } + + PUGI__FN const char_t* xpath_variable::get_string() const + { + const char_t* value = (_type == xpath_type_string) ? static_cast(this)->value : 0; + return value ? value : PUGIXML_TEXT(""); + } + + PUGI__FN const xpath_node_set& xpath_variable::get_node_set() const + { + return (_type == xpath_type_node_set) ? static_cast(this)->value : impl::dummy_node_set; + } + + PUGI__FN bool xpath_variable::set(bool value) + { + if (_type != xpath_type_boolean) return false; + + static_cast(this)->value = value; + return true; + } + + PUGI__FN bool xpath_variable::set(double value) + { + if (_type != xpath_type_number) return false; + + static_cast(this)->value = value; + return true; + } + + PUGI__FN bool xpath_variable::set(const char_t* value) + { + if (_type != xpath_type_string) return false; + + impl::xpath_variable_string* var = static_cast(this); + + // duplicate string + size_t size = (impl::strlength(value) + 1) * sizeof(char_t); + + char_t* copy = static_cast(impl::xml_memory::allocate(size)); + if (!copy) return false; + + memcpy(copy, value, size); + + // replace old string + if (var->value) impl::xml_memory::deallocate(var->value); + var->value = copy; + + return true; + } + + PUGI__FN bool xpath_variable::set(const xpath_node_set& value) + { + if (_type != xpath_type_node_set) return false; + + static_cast(this)->value = value; + return true; + } + + PUGI__FN xpath_variable_set::xpath_variable_set() + { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) _data[i] = 0; + } + + PUGI__FN xpath_variable_set::~xpath_variable_set() + { + for (size_t i = 0; i < sizeof(_data) / sizeof(_data[0]); ++i) + { + xpath_variable* var = _data[i]; + + while (var) + { + xpath_variable* next = var->_next; + + impl::delete_xpath_variable(var->_type, var); + + var = next; + } + } + } + + PUGI__FN xpath_variable* xpath_variable_set::find(const char_t* name) const + { + const size_t hash_size = sizeof(_data) / sizeof(_data[0]); + size_t hash = impl::hash_string(name) % hash_size; + + // look for existing variable + for (xpath_variable* var = _data[hash]; var; var = var->_next) + if (impl::strequal(var->name(), name)) + return var; + + return 0; + } + + PUGI__FN xpath_variable* xpath_variable_set::add(const char_t* name, xpath_value_type type) + { + const size_t hash_size = sizeof(_data) / sizeof(_data[0]); + size_t hash = impl::hash_string(name) % hash_size; + + // look for existing variable + for (xpath_variable* var = _data[hash]; var; var = var->_next) + if (impl::strequal(var->name(), name)) + return var->type() == type ? var : 0; + + // add new variable + xpath_variable* result = impl::new_xpath_variable(type, name); + + if (result) + { + result->_type = type; + result->_next = _data[hash]; + + _data[hash] = result; + } + + return result; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, bool value) + { + xpath_variable* var = add(name, xpath_type_boolean); + return var ? var->set(value) : false; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, double value) + { + xpath_variable* var = add(name, xpath_type_number); + return var ? var->set(value) : false; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, const char_t* value) + { + xpath_variable* var = add(name, xpath_type_string); + return var ? var->set(value) : false; + } + + PUGI__FN bool xpath_variable_set::set(const char_t* name, const xpath_node_set& value) + { + xpath_variable* var = add(name, xpath_type_node_set); + return var ? var->set(value) : false; + } + + PUGI__FN xpath_variable* xpath_variable_set::get(const char_t* name) + { + return find(name); + } + + PUGI__FN const xpath_variable* xpath_variable_set::get(const char_t* name) const + { + return find(name); + } + + PUGI__FN xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables): _impl(0) + { + impl::xpath_query_impl* qimpl = impl::xpath_query_impl::create(); + + if (!qimpl) + { + #ifdef PUGIXML_NO_EXCEPTIONS + _result.error = "Out of memory"; + #else + throw std::bad_alloc(); + #endif + } + else + { + impl::buffer_holder impl_holder(qimpl, impl::xpath_query_impl::destroy); + + qimpl->root = impl::xpath_parser::parse(query, variables, &qimpl->alloc, &_result); + + if (qimpl->root) + { + _impl = static_cast(impl_holder.release()); + _result.error = 0; + } + } + } + + PUGI__FN xpath_query::~xpath_query() + { + impl::xpath_query_impl::destroy(_impl); + } + + PUGI__FN xpath_value_type xpath_query::return_type() const + { + if (!_impl) return xpath_type_none; + + return static_cast(_impl)->root->rettype(); + } + + PUGI__FN bool xpath_query::evaluate_boolean(const xpath_node& n) const + { + if (!_impl) return false; + + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) return false; + #endif + + return static_cast(_impl)->root->eval_boolean(c, sd.stack); + } + + PUGI__FN double xpath_query::evaluate_number(const xpath_node& n) const + { + if (!_impl) return impl::gen_nan(); + + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) return impl::gen_nan(); + #endif + + return static_cast(_impl)->root->eval_number(c, sd.stack); + } + +#ifndef PUGIXML_NO_STL + PUGI__FN string_t xpath_query::evaluate_string(const xpath_node& n) const + { + impl::xpath_stack_data sd; + + return impl::evaluate_string_impl(static_cast(_impl), n, sd).c_str(); + } +#endif + + PUGI__FN size_t xpath_query::evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const + { + impl::xpath_stack_data sd; + + impl::xpath_string r = impl::evaluate_string_impl(static_cast(_impl), n, sd); + + size_t full_size = r.length() + 1; + + if (capacity > 0) + { + size_t size = (full_size < capacity) ? full_size : capacity; + assert(size > 0); + + memcpy(buffer, r.c_str(), (size - 1) * sizeof(char_t)); + buffer[size - 1] = 0; + } + + return full_size; + } + + PUGI__FN xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const + { + if (!_impl) return xpath_node_set(); + + impl::xpath_ast_node* root = static_cast(_impl)->root; + + if (root->rettype() != xpath_type_node_set) + { + #ifdef PUGIXML_NO_EXCEPTIONS + return xpath_node_set(); + #else + xpath_parse_result res; + res.error = "Expression does not evaluate to node set"; + + throw xpath_exception(res); + #endif + } + + impl::xpath_context c(n, 1, 1); + impl::xpath_stack_data sd; + + #ifdef PUGIXML_NO_EXCEPTIONS + if (setjmp(sd.error_handler)) return xpath_node_set(); + #endif + + impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack); + + return xpath_node_set(r.begin(), r.end(), r.type()); + } + + PUGI__FN const xpath_parse_result& xpath_query::result() const + { + return _result; + } + + PUGI__FN static void unspecified_bool_xpath_query(xpath_query***) + { + } + + PUGI__FN xpath_query::operator xpath_query::unspecified_bool_type() const + { + return _impl ? unspecified_bool_xpath_query : 0; + } + + PUGI__FN bool xpath_query::operator!() const + { + return !_impl; + } + + PUGI__FN xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables) const + { + xpath_query q(query, variables); + return select_single_node(q); + } + + PUGI__FN xpath_node xml_node::select_single_node(const xpath_query& query) const + { + xpath_node_set s = query.evaluate_node_set(*this); + return s.empty() ? xpath_node() : s.first(); + } + + PUGI__FN xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables) const + { + xpath_query q(query, variables); + return select_nodes(q); + } + + PUGI__FN xpath_node_set xml_node::select_nodes(const xpath_query& query) const + { + return query.evaluate_node_set(*this); + } +} + +#endif + +#ifdef __BORLANDC__ +# pragma option pop +#endif + +// Intel C++ does not properly keep warning state for function templates, +// so popping warning state at the end of translation unit leads to warnings in the middle. +#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) +# pragma warning(pop) +#endif + +// Undefine all local macros (makes sure we're not leaking macros in header-only mode) +#undef PUGI__NO_INLINE +#undef PUGI__STATIC_ASSERT +#undef PUGI__DMC_VOLATILE +#undef PUGI__MSVC_CRT_VERSION +#undef PUGI__NS_BEGIN +#undef PUGI__NS_END +#undef PUGI__FN +#undef PUGI__FN_NO_INLINE +#undef PUGI__IS_CHARTYPE_IMPL +#undef PUGI__IS_CHARTYPE +#undef PUGI__IS_CHARTYPEX +#undef PUGI__SKIPWS +#undef PUGI__OPTSET +#undef PUGI__PUSHNODE +#undef PUGI__POPNODE +#undef PUGI__SCANFOR +#undef PUGI__SCANWHILE +#undef PUGI__ENDSEG +#undef PUGI__THROW_ERROR +#undef PUGI__CHECK_ERROR + +#endif + +/** + * Copyright (c) 2006-2014 Arseny Kapoulkine + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ diff --git a/third_party/pugixml/pugixml.hpp b/deps/src/pugixml/pugixml.hpp similarity index 92% rename from third_party/pugixml/pugixml.hpp rename to deps/src/pugixml/pugixml.hpp index 894c34dcb..6fb99be48 100644 --- a/third_party/pugixml/pugixml.hpp +++ b/deps/src/pugixml/pugixml.hpp @@ -1,1265 +1,1332 @@ -/** - * pugixml parser - version 1.2 - * -------------------------------------------------------- - * Copyright (C) 2006-2012, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) - * Report bugs and download new versions at http://pugixml.org/ - * - * This library is distributed under the MIT License. See notice at the end - * of this file. - * - * This work is based on the pugxml parser, which is: - * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) - */ - -#ifndef PUGIXML_VERSION -// Define version macro; evaluates to major * 100 + minor so that it's safe to use in less-than comparisons -# define PUGIXML_VERSION 120 -#endif - -// Include user configuration file (this can define various configuration macros) -#include "pugiconfig.hpp" - -#ifndef HEADER_PUGIXML_HPP -#define HEADER_PUGIXML_HPP - -// Include stddef.h for size_t and ptrdiff_t -#include - -// Include exception header for XPath -#if !defined(PUGIXML_NO_XPATH) && !defined(PUGIXML_NO_EXCEPTIONS) -# include -#endif - -// Include STL headers -#ifndef PUGIXML_NO_STL -# include -# include -# include -#endif - -// Macro for deprecated features -#ifndef PUGIXML_DEPRECATED -# if defined(__GNUC__) -# define PUGIXML_DEPRECATED __attribute__((deprecated)) -# elif defined(_MSC_VER) && _MSC_VER >= 1300 -# define PUGIXML_DEPRECATED __declspec(deprecated) -# else -# define PUGIXML_DEPRECATED -# endif -#endif - -// If no API is defined, assume default -#ifndef PUGIXML_API -# define PUGIXML_API -#endif - -// If no API for classes is defined, assume default -#ifndef PUGIXML_CLASS -# define PUGIXML_CLASS PUGIXML_API -#endif - -// If no API for functions is defined, assume default -#ifndef PUGIXML_FUNCTION -# define PUGIXML_FUNCTION PUGIXML_API -#endif - -// Character interface macros -#ifdef PUGIXML_WCHAR_MODE -# define PUGIXML_TEXT(t) L ## t -# define PUGIXML_CHAR wchar_t -#else -# define PUGIXML_TEXT(t) t -# define PUGIXML_CHAR char -#endif - -namespace pugi -{ - // Character type used for all internal storage and operations; depends on PUGIXML_WCHAR_MODE - typedef PUGIXML_CHAR char_t; - -#ifndef PUGIXML_NO_STL - // String type used for operations that work with STL string; depends on PUGIXML_WCHAR_MODE - typedef std::basic_string, std::allocator > string_t; -#endif -} - -// The PugiXML namespace -namespace pugi -{ - // Tree node types - enum xml_node_type - { - node_null, // Empty (null) node handle - node_document, // A document tree's absolute root - node_element, // Element tag, i.e. '' - node_pcdata, // Plain character data, i.e. 'text' - node_cdata, // Character data, i.e. '' - node_comment, // Comment tag, i.e. '' - node_pi, // Processing instruction, i.e. '' - node_declaration, // Document declaration, i.e. '' - node_doctype // Document type declaration, i.e. '' - }; - - // Parsing options - - // Minimal parsing mode (equivalent to turning all other flags off). - // Only elements and PCDATA sections are added to the DOM tree, no text conversions are performed. - const unsigned int parse_minimal = 0x0000; - - // This flag determines if processing instructions (node_pi) are added to the DOM tree. This flag is off by default. - const unsigned int parse_pi = 0x0001; - - // This flag determines if comments (node_comment) are added to the DOM tree. This flag is off by default. - const unsigned int parse_comments = 0x0002; - - // This flag determines if CDATA sections (node_cdata) are added to the DOM tree. This flag is on by default. - const unsigned int parse_cdata = 0x0004; - - // This flag determines if plain character data (node_pcdata) that consist only of whitespace are added to the DOM tree. - // This flag is off by default; turning it on usually results in slower parsing and more memory consumption. - const unsigned int parse_ws_pcdata = 0x0008; - - // This flag determines if character and entity references are expanded during parsing. This flag is on by default. - const unsigned int parse_escapes = 0x0010; - - // This flag determines if EOL characters are normalized (converted to #xA) during parsing. This flag is on by default. - const unsigned int parse_eol = 0x0020; - - // This flag determines if attribute values are normalized using CDATA normalization rules during parsing. This flag is on by default. - const unsigned int parse_wconv_attribute = 0x0040; - - // This flag determines if attribute values are normalized using NMTOKENS normalization rules during parsing. This flag is off by default. - const unsigned int parse_wnorm_attribute = 0x0080; - - // This flag determines if document declaration (node_declaration) is added to the DOM tree. This flag is off by default. - const unsigned int parse_declaration = 0x0100; - - // This flag determines if document type declaration (node_doctype) is added to the DOM tree. This flag is off by default. - const unsigned int parse_doctype = 0x0200; - - // This flag determines if plain character data (node_pcdata) that is the only child of the parent node and that consists only - // of whitespace is added to the DOM tree. - // This flag is off by default; turning it on may result in slower parsing and more memory consumption. - const unsigned int parse_ws_pcdata_single = 0x0400; - - // The default parsing mode. - // Elements, PCDATA and CDATA sections are added to the DOM tree, character/reference entities are expanded, - // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. - const unsigned int parse_default = parse_cdata | parse_escapes | parse_wconv_attribute | parse_eol; - - // The full parsing mode. - // Nodes of all types are added to the DOM tree, character/reference entities are expanded, - // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. - const unsigned int parse_full = parse_default | parse_pi | parse_comments | parse_declaration | parse_doctype; - - // These flags determine the encoding of input data for XML document - enum xml_encoding - { - encoding_auto, // Auto-detect input encoding using BOM or < / class xml_object_range - { - public: - typedef It const_iterator; - - xml_object_range(It b, It e): _begin(b), _end(e) - { - } - - It begin() const { return _begin; } - It end() const { return _end; } - - private: - It _begin, _end; - }; - - // Writer interface for node printing (see xml_node::print) - class PUGIXML_CLASS xml_writer - { - public: - virtual ~xml_writer() {} - - // Write memory chunk into stream/file/whatever - virtual void write(const void* data, size_t size) = 0; - }; - - // xml_writer implementation for FILE* - class PUGIXML_CLASS xml_writer_file: public xml_writer - { - public: - // Construct writer from a FILE* object; void* is used to avoid header dependencies on stdio - xml_writer_file(void* file); - - virtual void write(const void* data, size_t size); - - private: - void* file; - }; - - #ifndef PUGIXML_NO_STL - // xml_writer implementation for streams - class PUGIXML_CLASS xml_writer_stream: public xml_writer - { - public: - // Construct writer from an output stream object - xml_writer_stream(std::basic_ostream >& stream); - xml_writer_stream(std::basic_ostream >& stream); - - virtual void write(const void* data, size_t size); - - private: - std::basic_ostream >* narrow_stream; - std::basic_ostream >* wide_stream; - }; - #endif - - // A light-weight handle for manipulating attributes in DOM tree - class PUGIXML_CLASS xml_attribute - { - friend class xml_attribute_iterator; - friend class xml_node; - - private: - xml_attribute_struct* _attr; - - typedef void (*unspecified_bool_type)(xml_attribute***); - - public: - // Default constructor. Constructs an empty attribute. - xml_attribute(); - - // Constructs attribute from internal pointer - explicit xml_attribute(xml_attribute_struct* attr); - - // Safe bool conversion operator - operator unspecified_bool_type() const; - - // Borland C++ workaround - bool operator!() const; - - // Comparison operators (compares wrapped attribute pointers) - bool operator==(const xml_attribute& r) const; - bool operator!=(const xml_attribute& r) const; - bool operator<(const xml_attribute& r) const; - bool operator>(const xml_attribute& r) const; - bool operator<=(const xml_attribute& r) const; - bool operator>=(const xml_attribute& r) const; - - // Check if attribute is empty - bool empty() const; - - // Get attribute name/value, or "" if attribute is empty - const char_t* name() const; - const char_t* value() const; - - // Get attribute value, or the default value if attribute is empty - const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; - - // Get attribute value as a number, or the default value if conversion did not succeed or attribute is empty - int as_int(int def = 0) const; - unsigned int as_uint(unsigned int def = 0) const; - double as_double(double def = 0) const; - float as_float(float def = 0) const; - - // Get attribute value as bool (returns true if first character is in '1tTyY' set), or the default value if attribute is empty - bool as_bool(bool def = false) const; - - // Set attribute name/value (returns false if attribute is empty or there is not enough memory) - bool set_name(const char_t* rhs); - bool set_value(const char_t* rhs); - - // Set attribute value with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") - bool set_value(int rhs); - bool set_value(unsigned int rhs); - bool set_value(double rhs); - bool set_value(bool rhs); - - // Set attribute value (equivalent to set_value without error checking) - xml_attribute& operator=(const char_t* rhs); - xml_attribute& operator=(int rhs); - xml_attribute& operator=(unsigned int rhs); - xml_attribute& operator=(double rhs); - xml_attribute& operator=(bool rhs); - - // Get next/previous attribute in the attribute list of the parent node - xml_attribute next_attribute() const; - xml_attribute previous_attribute() const; - - // Get hash value (unique for handles to the same object) - size_t hash_value() const; - - // Get internal pointer - xml_attribute_struct* internal_object() const; - }; - -#ifdef __BORLANDC__ - // Borland C++ workaround - bool PUGIXML_FUNCTION operator&&(const xml_attribute& lhs, bool rhs); - bool PUGIXML_FUNCTION operator||(const xml_attribute& lhs, bool rhs); -#endif - - // A light-weight handle for manipulating nodes in DOM tree - class PUGIXML_CLASS xml_node - { - friend class xml_attribute_iterator; - friend class xml_node_iterator; - friend class xml_named_node_iterator; - - protected: - xml_node_struct* _root; - - typedef void (*unspecified_bool_type)(xml_node***); - - public: - // Default constructor. Constructs an empty node. - xml_node(); - - // Constructs node from internal pointer - explicit xml_node(xml_node_struct* p); - - // Safe bool conversion operator - operator unspecified_bool_type() const; - - // Borland C++ workaround - bool operator!() const; - - // Comparison operators (compares wrapped node pointers) - bool operator==(const xml_node& r) const; - bool operator!=(const xml_node& r) const; - bool operator<(const xml_node& r) const; - bool operator>(const xml_node& r) const; - bool operator<=(const xml_node& r) const; - bool operator>=(const xml_node& r) const; - - // Check if node is empty. - bool empty() const; - - // Get node type - xml_node_type type() const; - - // Get node name/value, or "" if node is empty or it has no name/value - const char_t* name() const; - const char_t* value() const; - - // Get attribute list - xml_attribute first_attribute() const; - xml_attribute last_attribute() const; - - // Get children list - xml_node first_child() const; - xml_node last_child() const; - - // Get next/previous sibling in the children list of the parent node - xml_node next_sibling() const; - xml_node previous_sibling() const; - - // Get parent node - xml_node parent() const; - - // Get root of DOM tree this node belongs to - xml_node root() const; - - // Get text object for the current node - xml_text text() const; - - // Get child, attribute or next/previous sibling with the specified name - xml_node child(const char_t* name) const; - xml_attribute attribute(const char_t* name) const; - xml_node next_sibling(const char_t* name) const; - xml_node previous_sibling(const char_t* name) const; - - // Get child value of current node; that is, value of the first child node of type PCDATA/CDATA - const char_t* child_value() const; - - // Get child value of child with specified name. Equivalent to child(name).child_value(). - const char_t* child_value(const char_t* name) const; - - // Set node name/value (returns false if node is empty, there is not enough memory, or node can not have name/value) - bool set_name(const char_t* rhs); - bool set_value(const char_t* rhs); - - // Add attribute with specified name. Returns added attribute, or empty attribute on errors. - xml_attribute append_attribute(const char_t* name); - xml_attribute prepend_attribute(const char_t* name); - xml_attribute insert_attribute_after(const char_t* name, const xml_attribute& attr); - xml_attribute insert_attribute_before(const char_t* name, const xml_attribute& attr); - - // Add a copy of the specified attribute. Returns added attribute, or empty attribute on errors. - xml_attribute append_copy(const xml_attribute& proto); - xml_attribute prepend_copy(const xml_attribute& proto); - xml_attribute insert_copy_after(const xml_attribute& proto, const xml_attribute& attr); - xml_attribute insert_copy_before(const xml_attribute& proto, const xml_attribute& attr); - - // Add child node with specified type. Returns added node, or empty node on errors. - xml_node append_child(xml_node_type type = node_element); - xml_node prepend_child(xml_node_type type = node_element); - xml_node insert_child_after(xml_node_type type, const xml_node& node); - xml_node insert_child_before(xml_node_type type, const xml_node& node); - - // Add child element with specified name. Returns added node, or empty node on errors. - xml_node append_child(const char_t* name); - xml_node prepend_child(const char_t* name); - xml_node insert_child_after(const char_t* name, const xml_node& node); - xml_node insert_child_before(const char_t* name, const xml_node& node); - - // Add a copy of the specified node as a child. Returns added node, or empty node on errors. - xml_node append_copy(const xml_node& proto); - xml_node prepend_copy(const xml_node& proto); - xml_node insert_copy_after(const xml_node& proto, const xml_node& node); - xml_node insert_copy_before(const xml_node& proto, const xml_node& node); - - // Remove specified attribute - bool remove_attribute(const xml_attribute& a); - bool remove_attribute(const char_t* name); - - // Remove specified child - bool remove_child(const xml_node& n); - bool remove_child(const char_t* name); - - // Find attribute using predicate. Returns first attribute for which predicate returned true. - template xml_attribute find_attribute(Predicate pred) const - { - if (!_root) return xml_attribute(); - - for (xml_attribute attrib = first_attribute(); attrib; attrib = attrib.next_attribute()) - if (pred(attrib)) - return attrib; - - return xml_attribute(); - } - - // Find child node using predicate. Returns first child for which predicate returned true. - template xml_node find_child(Predicate pred) const - { - if (!_root) return xml_node(); - - for (xml_node node = first_child(); node; node = node.next_sibling()) - if (pred(node)) - return node; - - return xml_node(); - } - - // Find node from subtree using predicate. Returns first node from subtree (depth-first), for which predicate returned true. - template xml_node find_node(Predicate pred) const - { - if (!_root) return xml_node(); - - xml_node cur = first_child(); - - while (cur._root && cur._root != _root) - { - if (pred(cur)) return cur; - - if (cur.first_child()) cur = cur.first_child(); - else if (cur.next_sibling()) cur = cur.next_sibling(); - else - { - while (!cur.next_sibling() && cur._root != _root) cur = cur.parent(); - - if (cur._root != _root) cur = cur.next_sibling(); - } - } - - return xml_node(); - } - - // Find child node by attribute name/value - xml_node find_child_by_attribute(const char_t* name, const char_t* attr_name, const char_t* attr_value) const; - xml_node find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const; - - #ifndef PUGIXML_NO_STL - // Get the absolute node path from root as a text string. - string_t path(char_t delimiter = '/') const; - #endif - - // Search for a node by path consisting of node names and . or .. elements. - xml_node first_element_by_path(const char_t* path, char_t delimiter = '/') const; - - // Recursively traverse subtree with xml_tree_walker - bool traverse(xml_tree_walker& walker); - - #ifndef PUGIXML_NO_XPATH - // Select single node by evaluating XPath query. Returns first node from the resulting node set. - xpath_node select_single_node(const char_t* query, xpath_variable_set* variables = 0) const; - xpath_node select_single_node(const xpath_query& query) const; - - // Select node set by evaluating XPath query - xpath_node_set select_nodes(const char_t* query, xpath_variable_set* variables = 0) const; - xpath_node_set select_nodes(const xpath_query& query) const; - #endif - - // Print subtree using a writer object - void print(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; - - #ifndef PUGIXML_NO_STL - // Print subtree to stream - void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; - void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, unsigned int depth = 0) const; - #endif - - // Child nodes iterators - typedef xml_node_iterator iterator; - - iterator begin() const; - iterator end() const; - - // Attribute iterators - typedef xml_attribute_iterator attribute_iterator; - - attribute_iterator attributes_begin() const; - attribute_iterator attributes_end() const; - - // Range-based for support - xml_object_range children() const; - xml_object_range children(const char_t* name) const; - xml_object_range attributes() const; - - // Get node offset in parsed file/string (in char_t units) for debugging purposes - ptrdiff_t offset_debug() const; - - // Get hash value (unique for handles to the same object) - size_t hash_value() const; - - // Get internal pointer - xml_node_struct* internal_object() const; - }; - -#ifdef __BORLANDC__ - // Borland C++ workaround - bool PUGIXML_FUNCTION operator&&(const xml_node& lhs, bool rhs); - bool PUGIXML_FUNCTION operator||(const xml_node& lhs, bool rhs); -#endif - - // A helper for working with text inside PCDATA nodes - class PUGIXML_CLASS xml_text - { - friend class xml_node; - - xml_node_struct* _root; - - typedef void (*unspecified_bool_type)(xml_text***); - - explicit xml_text(xml_node_struct* root); - - xml_node_struct* _data_new(); - xml_node_struct* _data() const; - - public: - // Default constructor. Constructs an empty object. - xml_text(); - - // Safe bool conversion operator - operator unspecified_bool_type() const; - - // Borland C++ workaround - bool operator!() const; - - // Check if text object is empty - bool empty() const; - - // Get text, or "" if object is empty - const char_t* get() const; - - // Get text, or the default value if object is empty - const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; - - // Get text as a number, or the default value if conversion did not succeed or object is empty - int as_int(int def = 0) const; - unsigned int as_uint(unsigned int def = 0) const; - double as_double(double def = 0) const; - float as_float(float def = 0) const; - - // Get text as bool (returns true if first character is in '1tTyY' set), or the default value if object is empty - bool as_bool(bool def = false) const; - - // Set text (returns false if object is empty or there is not enough memory) - bool set(const char_t* rhs); - - // Set text with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") - bool set(int rhs); - bool set(unsigned int rhs); - bool set(double rhs); - bool set(bool rhs); - - // Set text (equivalent to set without error checking) - xml_text& operator=(const char_t* rhs); - xml_text& operator=(int rhs); - xml_text& operator=(unsigned int rhs); - xml_text& operator=(double rhs); - xml_text& operator=(bool rhs); - - // Get the data node (node_pcdata or node_cdata) for this object - xml_node data() const; - }; - -#ifdef __BORLANDC__ - // Borland C++ workaround - bool PUGIXML_FUNCTION operator&&(const xml_text& lhs, bool rhs); - bool PUGIXML_FUNCTION operator||(const xml_text& lhs, bool rhs); -#endif - - // Child node iterator (a bidirectional iterator over a collection of xml_node) - class PUGIXML_CLASS xml_node_iterator - { - friend class xml_node; - - private: - mutable xml_node _wrap; - xml_node _parent; - - xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent); - - public: - // Iterator traits - typedef ptrdiff_t difference_type; - typedef xml_node value_type; - typedef xml_node* pointer; - typedef xml_node& reference; - - #ifndef PUGIXML_NO_STL - typedef std::bidirectional_iterator_tag iterator_category; - #endif - - // Default constructor - xml_node_iterator(); - - // Construct an iterator which points to the specified node - xml_node_iterator(const xml_node& node); - - // Iterator operators - bool operator==(const xml_node_iterator& rhs) const; - bool operator!=(const xml_node_iterator& rhs) const; - - xml_node& operator*() const; - xml_node* operator->() const; - - const xml_node_iterator& operator++(); - xml_node_iterator operator++(int); - - const xml_node_iterator& operator--(); - xml_node_iterator operator--(int); - }; - - // Attribute iterator (a bidirectional iterator over a collection of xml_attribute) - class PUGIXML_CLASS xml_attribute_iterator - { - friend class xml_node; - - private: - mutable xml_attribute _wrap; - xml_node _parent; - - xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent); - - public: - // Iterator traits - typedef ptrdiff_t difference_type; - typedef xml_attribute value_type; - typedef xml_attribute* pointer; - typedef xml_attribute& reference; - - #ifndef PUGIXML_NO_STL - typedef std::bidirectional_iterator_tag iterator_category; - #endif - - // Default constructor - xml_attribute_iterator(); - - // Construct an iterator which points to the specified attribute - xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent); - - // Iterator operators - bool operator==(const xml_attribute_iterator& rhs) const; - bool operator!=(const xml_attribute_iterator& rhs) const; - - xml_attribute& operator*() const; - xml_attribute* operator->() const; - - const xml_attribute_iterator& operator++(); - xml_attribute_iterator operator++(int); - - const xml_attribute_iterator& operator--(); - xml_attribute_iterator operator--(int); - }; - - // Named node range helper - class xml_named_node_iterator - { - public: - // Iterator traits - typedef ptrdiff_t difference_type; - typedef xml_node value_type; - typedef xml_node* pointer; - typedef xml_node& reference; - - #ifndef PUGIXML_NO_STL - typedef std::forward_iterator_tag iterator_category; - #endif - - // Default constructor - xml_named_node_iterator(); - - // Construct an iterator which points to the specified node - xml_named_node_iterator(const xml_node& node, const char_t* name); - - // Iterator operators - bool operator==(const xml_named_node_iterator& rhs) const; - bool operator!=(const xml_named_node_iterator& rhs) const; - - xml_node& operator*() const; - xml_node* operator->() const; - - const xml_named_node_iterator& operator++(); - xml_named_node_iterator operator++(int); - - private: - mutable xml_node _node; - const char_t* _name; - }; - - // Abstract tree walker class (see xml_node::traverse) - class PUGIXML_CLASS xml_tree_walker - { - friend class xml_node; - - private: - int _depth; - - protected: - // Get current traversal depth - int depth() const; - - public: - xml_tree_walker(); - virtual ~xml_tree_walker(); - - // Callback that is called when traversal begins - virtual bool begin(xml_node& node); - - // Callback that is called for each node traversed - virtual bool for_each(xml_node& node) = 0; - - // Callback that is called when traversal ends - virtual bool end(xml_node& node); - }; - - // Parsing status, returned as part of xml_parse_result object - enum xml_parse_status - { - status_ok = 0, // No error - - status_file_not_found, // File was not found during load_file() - status_io_error, // Error reading from file/stream - status_out_of_memory, // Could not allocate memory - status_internal_error, // Internal error occurred - - status_unrecognized_tag, // Parser could not determine tag type - - status_bad_pi, // Parsing error occurred while parsing document declaration/processing instruction - status_bad_comment, // Parsing error occurred while parsing comment - status_bad_cdata, // Parsing error occurred while parsing CDATA section - status_bad_doctype, // Parsing error occurred while parsing document type declaration - status_bad_pcdata, // Parsing error occurred while parsing PCDATA section - status_bad_start_element, // Parsing error occurred while parsing start element tag - status_bad_attribute, // Parsing error occurred while parsing element attribute - status_bad_end_element, // Parsing error occurred while parsing end element tag - status_end_element_mismatch // There was a mismatch of start-end tags (closing tag had incorrect name, some tag was not closed or there was an excessive closing tag) - }; - - // Parsing result - struct PUGIXML_CLASS xml_parse_result - { - // Parsing status (see xml_parse_status) - xml_parse_status status; - - // Last parsed offset (in char_t units from start of input data) - ptrdiff_t offset; - - // Source document encoding - xml_encoding encoding; - - // Default constructor, initializes object to failed state - xml_parse_result(); - - // Cast to bool operator - operator bool() const; - - // Get error description - const char* description() const; - }; - - // Document class (DOM tree root) - class PUGIXML_CLASS xml_document: public xml_node - { - private: - char_t* _buffer; - - char _memory[192]; - - // Non-copyable semantics - xml_document(const xml_document&); - const xml_document& operator=(const xml_document&); - - void create(); - void destroy(); - - xml_parse_result load_buffer_impl(void* contents, size_t size, unsigned int options, xml_encoding encoding, bool is_mutable, bool own); - - public: - // Default constructor, makes empty document - xml_document(); - - // Destructor, invalidates all node/attribute handles to this document - ~xml_document(); - - // Removes all nodes, leaving the empty document - void reset(); - - // Removes all nodes, then copies the entire contents of the specified document - void reset(const xml_document& proto); - - #ifndef PUGIXML_NO_STL - // Load document from stream. - xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); - xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default); - #endif - - // Load document from zero-terminated string. No encoding conversions are applied. - xml_parse_result load(const char_t* contents, unsigned int options = parse_default); - - // Load document from file - xml_parse_result load_file(const char* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); - xml_parse_result load_file(const wchar_t* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); - - // Load document from buffer. Copies/converts the buffer, so it may be deleted or changed after the function returns. - xml_parse_result load_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); - - // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). - // You should ensure that buffer data will persist throughout the document's lifetime, and free the buffer memory manually once document is destroyed. - xml_parse_result load_buffer_inplace(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); - - // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). - // You should allocate the buffer with pugixml allocation function; document will free the buffer when it is no longer needed (you can't use it anymore). - xml_parse_result load_buffer_inplace_own(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); - - // Save XML document to writer (semantics is slightly different from xml_node::print, see documentation for details). - void save(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; - - #ifndef PUGIXML_NO_STL - // Save XML document to stream (semantics is slightly different from xml_node::print, see documentation for details). - void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; - void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default) const; - #endif - - // Save XML to file - bool save_file(const char* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; - bool save_file(const wchar_t* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; - - // Get document element - xml_node document_element() const; - }; - -#ifndef PUGIXML_NO_XPATH - // XPath query return type - enum xpath_value_type - { - xpath_type_none, // Unknown type (query failed to compile) - xpath_type_node_set, // Node set (xpath_node_set) - xpath_type_number, // Number - xpath_type_string, // String - xpath_type_boolean // Boolean - }; - - // XPath parsing result - struct PUGIXML_CLASS xpath_parse_result - { - // Error message (0 if no error) - const char* error; - - // Last parsed offset (in char_t units from string start) - ptrdiff_t offset; - - // Default constructor, initializes object to failed state - xpath_parse_result(); - - // Cast to bool operator - operator bool() const; - - // Get error description - const char* description() const; - }; - - // A single XPath variable - class PUGIXML_CLASS xpath_variable - { - friend class xpath_variable_set; - - protected: - xpath_value_type _type; - xpath_variable* _next; - - xpath_variable(); - - // Non-copyable semantics - xpath_variable(const xpath_variable&); - xpath_variable& operator=(const xpath_variable&); - - public: - // Get variable name - const char_t* name() const; - - // Get variable type - xpath_value_type type() const; - - // Get variable value; no type conversion is performed, default value (false, NaN, empty string, empty node set) is returned on type mismatch error - bool get_boolean() const; - double get_number() const; - const char_t* get_string() const; - const xpath_node_set& get_node_set() const; - - // Set variable value; no type conversion is performed, false is returned on type mismatch error - bool set(bool value); - bool set(double value); - bool set(const char_t* value); - bool set(const xpath_node_set& value); - }; - - // A set of XPath variables - class PUGIXML_CLASS xpath_variable_set - { - private: - xpath_variable* _data[64]; - - // Non-copyable semantics - xpath_variable_set(const xpath_variable_set&); - xpath_variable_set& operator=(const xpath_variable_set&); - - xpath_variable* find(const char_t* name) const; - - public: - // Default constructor/destructor - xpath_variable_set(); - ~xpath_variable_set(); - - // Add a new variable or get the existing one, if the types match - xpath_variable* add(const char_t* name, xpath_value_type type); - - // Set value of an existing variable; no type conversion is performed, false is returned if there is no such variable or if types mismatch - bool set(const char_t* name, bool value); - bool set(const char_t* name, double value); - bool set(const char_t* name, const char_t* value); - bool set(const char_t* name, const xpath_node_set& value); - - // Get existing variable by name - xpath_variable* get(const char_t* name); - const xpath_variable* get(const char_t* name) const; - }; - - // A compiled XPath query object - class PUGIXML_CLASS xpath_query - { - private: - void* _impl; - xpath_parse_result _result; - - typedef void (*unspecified_bool_type)(xpath_query***); - - // Non-copyable semantics - xpath_query(const xpath_query&); - xpath_query& operator=(const xpath_query&); - - public: - // Construct a compiled object from XPath expression. - // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on compilation errors. - explicit xpath_query(const char_t* query, xpath_variable_set* variables = 0); - - // Destructor - ~xpath_query(); - - // Get query expression return type - xpath_value_type return_type() const; - - // Evaluate expression as boolean value in the specified context; performs type conversion if necessary. - // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. - bool evaluate_boolean(const xpath_node& n) const; - - // Evaluate expression as double value in the specified context; performs type conversion if necessary. - // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. - double evaluate_number(const xpath_node& n) const; - - #ifndef PUGIXML_NO_STL - // Evaluate expression as string value in the specified context; performs type conversion if necessary. - // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. - string_t evaluate_string(const xpath_node& n) const; - #endif - - // Evaluate expression as string value in the specified context; performs type conversion if necessary. - // At most capacity characters are written to the destination buffer, full result size is returned (includes terminating zero). - // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. - // If PUGIXML_NO_EXCEPTIONS is defined, returns empty set instead. - size_t evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const; - - // Evaluate expression as node set in the specified context. - // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors. - // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node set instead. - xpath_node_set evaluate_node_set(const xpath_node& n) const; - - // Get parsing result (used to get compilation errors in PUGIXML_NO_EXCEPTIONS mode) - const xpath_parse_result& result() const; - - // Safe bool conversion operator - operator unspecified_bool_type() const; - - // Borland C++ workaround - bool operator!() const; - }; - - #ifndef PUGIXML_NO_EXCEPTIONS - // XPath exception class - class PUGIXML_CLASS xpath_exception: public std::exception - { - private: - xpath_parse_result _result; - - public: - // Construct exception from parse result - explicit xpath_exception(const xpath_parse_result& result); - - // Get error message - virtual const char* what() const throw(); - - // Get parse result - const xpath_parse_result& result() const; - }; - #endif - - // XPath node class (either xml_node or xml_attribute) - class PUGIXML_CLASS xpath_node - { - private: - xml_node _node; - xml_attribute _attribute; - - typedef void (*unspecified_bool_type)(xpath_node***); - - public: - // Default constructor; constructs empty XPath node - xpath_node(); - - // Construct XPath node from XML node/attribute - xpath_node(const xml_node& node); - xpath_node(const xml_attribute& attribute, const xml_node& parent); - - // Get node/attribute, if any - xml_node node() const; - xml_attribute attribute() const; - - // Get parent of contained node/attribute - xml_node parent() const; - - // Safe bool conversion operator - operator unspecified_bool_type() const; - - // Borland C++ workaround - bool operator!() const; - - // Comparison operators - bool operator==(const xpath_node& n) const; - bool operator!=(const xpath_node& n) const; - }; - -#ifdef __BORLANDC__ - // Borland C++ workaround - bool PUGIXML_FUNCTION operator&&(const xpath_node& lhs, bool rhs); - bool PUGIXML_FUNCTION operator||(const xpath_node& lhs, bool rhs); -#endif - - // A fixed-size collection of XPath nodes - class PUGIXML_CLASS xpath_node_set - { - public: - // Collection type - enum type_t - { - type_unsorted, // Not ordered - type_sorted, // Sorted by document order (ascending) - type_sorted_reverse // Sorted by document order (descending) - }; - - // Constant iterator type - typedef const xpath_node* const_iterator; - - // Default constructor. Constructs empty set. - xpath_node_set(); - - // Constructs a set from iterator range; data is not checked for duplicates and is not sorted according to provided type, so be careful - xpath_node_set(const_iterator begin, const_iterator end, type_t type = type_unsorted); - - // Destructor - ~xpath_node_set(); - - // Copy constructor/assignment operator - xpath_node_set(const xpath_node_set& ns); - xpath_node_set& operator=(const xpath_node_set& ns); - - // Get collection type - type_t type() const; - - // Get collection size - size_t size() const; - - // Indexing operator - const xpath_node& operator[](size_t index) const; - - // Collection iterators - const_iterator begin() const; - const_iterator end() const; - - // Sort the collection in ascending/descending order by document order - void sort(bool reverse = false); - - // Get first node in the collection by document order - xpath_node first() const; - - // Check if collection is empty - bool empty() const; - - private: - type_t _type; - - xpath_node _storage; - - xpath_node* _begin; - xpath_node* _end; - - void _assign(const_iterator begin, const_iterator end); - }; -#endif - -#ifndef PUGIXML_NO_STL - // Convert wide string to UTF8 - std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const wchar_t* str); - std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const std::basic_string, std::allocator >& str); - - // Convert UTF8 to wide string - std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const char* str); - std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const std::basic_string, std::allocator >& str); -#endif - - // Memory allocation function interface; returns pointer to allocated memory or NULL on failure - typedef void* (*allocation_function)(size_t size); - - // Memory deallocation function interface - typedef void (*deallocation_function)(void* ptr); - - // Override default memory management functions. All subsequent allocations/deallocations will be performed via supplied functions. - void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate); - - // Get current memory management functions - allocation_function PUGIXML_FUNCTION get_memory_allocation_function(); - deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function(); -} - -#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) -namespace std -{ - // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) - std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_node_iterator&); - std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_attribute_iterator&); - std::forward_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_named_node_iterator&); -} -#endif - -#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) -namespace std -{ - // Workarounds for (non-standard) iterator category detection - std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_node_iterator&); - std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_attribute_iterator&); - std::forward_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_named_node_iterator&); -} -#endif - -#endif - -/** - * Copyright (c) 2006-2012 Arseny Kapoulkine - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - */ +/** + * pugixml parser - version 1.4 + * -------------------------------------------------------- + * Copyright (C) 2006-2014, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) + * Report bugs and download new versions at http://pugixml.org/ + * + * This library is distributed under the MIT License. See notice at the end + * of this file. + * + * This work is based on the pugxml parser, which is: + * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) + */ + +#ifndef PUGIXML_VERSION +// Define version macro; evaluates to major * 100 + minor so that it's safe to use in less-than comparisons +# define PUGIXML_VERSION 140 +#endif + +// Include user configuration file (this can define various configuration macros) +#include "pugiconfig.hpp" + +#ifndef HEADER_PUGIXML_HPP +#define HEADER_PUGIXML_HPP + +// Include stddef.h for size_t and ptrdiff_t +#include + +// Include exception header for XPath +#if !defined(PUGIXML_NO_XPATH) && !defined(PUGIXML_NO_EXCEPTIONS) +# include +#endif + +// Include STL headers +#ifndef PUGIXML_NO_STL +# include +# include +# include +#endif + +// Macro for deprecated features +#ifndef PUGIXML_DEPRECATED +# if defined(__GNUC__) +# define PUGIXML_DEPRECATED __attribute__((deprecated)) +# elif defined(_MSC_VER) && _MSC_VER >= 1300 +# define PUGIXML_DEPRECATED __declspec(deprecated) +# else +# define PUGIXML_DEPRECATED +# endif +#endif + +// If no API is defined, assume default +#ifndef PUGIXML_API +# define PUGIXML_API +#endif + +// If no API for classes is defined, assume default +#ifndef PUGIXML_CLASS +# define PUGIXML_CLASS PUGIXML_API +#endif + +// If no API for functions is defined, assume default +#ifndef PUGIXML_FUNCTION +# define PUGIXML_FUNCTION PUGIXML_API +#endif + +// If the platform is known to have long long support, enable long long functions +#ifndef PUGIXML_HAS_LONG_LONG +# if defined(__cplusplus) && __cplusplus >= 201103 +# define PUGIXML_HAS_LONG_LONG +# elif defined(_MSC_VER) && _MSC_VER >= 1400 +# define PUGIXML_HAS_LONG_LONG +# endif +#endif + +// Character interface macros +#ifdef PUGIXML_WCHAR_MODE +# define PUGIXML_TEXT(t) L ## t +# define PUGIXML_CHAR wchar_t +#else +# define PUGIXML_TEXT(t) t +# define PUGIXML_CHAR char +#endif + +namespace pugi +{ + // Character type used for all internal storage and operations; depends on PUGIXML_WCHAR_MODE + typedef PUGIXML_CHAR char_t; + +#ifndef PUGIXML_NO_STL + // String type used for operations that work with STL string; depends on PUGIXML_WCHAR_MODE + typedef std::basic_string, std::allocator > string_t; +#endif +} + +// The PugiXML namespace +namespace pugi +{ + // Tree node types + enum xml_node_type + { + node_null, // Empty (null) node handle + node_document, // A document tree's absolute root + node_element, // Element tag, i.e. '' + node_pcdata, // Plain character data, i.e. 'text' + node_cdata, // Character data, i.e. '' + node_comment, // Comment tag, i.e. '' + node_pi, // Processing instruction, i.e. '' + node_declaration, // Document declaration, i.e. '' + node_doctype // Document type declaration, i.e. '' + }; + + // Parsing options + + // Minimal parsing mode (equivalent to turning all other flags off). + // Only elements and PCDATA sections are added to the DOM tree, no text conversions are performed. + const unsigned int parse_minimal = 0x0000; + + // This flag determines if processing instructions (node_pi) are added to the DOM tree. This flag is off by default. + const unsigned int parse_pi = 0x0001; + + // This flag determines if comments (node_comment) are added to the DOM tree. This flag is off by default. + const unsigned int parse_comments = 0x0002; + + // This flag determines if CDATA sections (node_cdata) are added to the DOM tree. This flag is on by default. + const unsigned int parse_cdata = 0x0004; + + // This flag determines if plain character data (node_pcdata) that consist only of whitespace are added to the DOM tree. + // This flag is off by default; turning it on usually results in slower parsing and more memory consumption. + const unsigned int parse_ws_pcdata = 0x0008; + + // This flag determines if character and entity references are expanded during parsing. This flag is on by default. + const unsigned int parse_escapes = 0x0010; + + // This flag determines if EOL characters are normalized (converted to #xA) during parsing. This flag is on by default. + const unsigned int parse_eol = 0x0020; + + // This flag determines if attribute values are normalized using CDATA normalization rules during parsing. This flag is on by default. + const unsigned int parse_wconv_attribute = 0x0040; + + // This flag determines if attribute values are normalized using NMTOKENS normalization rules during parsing. This flag is off by default. + const unsigned int parse_wnorm_attribute = 0x0080; + + // This flag determines if document declaration (node_declaration) is added to the DOM tree. This flag is off by default. + const unsigned int parse_declaration = 0x0100; + + // This flag determines if document type declaration (node_doctype) is added to the DOM tree. This flag is off by default. + const unsigned int parse_doctype = 0x0200; + + // This flag determines if plain character data (node_pcdata) that is the only child of the parent node and that consists only + // of whitespace is added to the DOM tree. + // This flag is off by default; turning it on may result in slower parsing and more memory consumption. + const unsigned int parse_ws_pcdata_single = 0x0400; + + // This flag determines if leading and trailing whitespace is to be removed from plain character data. This flag is off by default. + const unsigned int parse_trim_pcdata = 0x0800; + + // This flag determines if plain character data that does not have a parent node is added to the DOM tree, and if an empty document + // is a valid document. This flag is off by default. + const unsigned int parse_fragment = 0x1000; + + // The default parsing mode. + // Elements, PCDATA and CDATA sections are added to the DOM tree, character/reference entities are expanded, + // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. + const unsigned int parse_default = parse_cdata | parse_escapes | parse_wconv_attribute | parse_eol; + + // The full parsing mode. + // Nodes of all types are added to the DOM tree, character/reference entities are expanded, + // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. + const unsigned int parse_full = parse_default | parse_pi | parse_comments | parse_declaration | parse_doctype; + + // These flags determine the encoding of input data for XML document + enum xml_encoding + { + encoding_auto, // Auto-detect input encoding using BOM or < / class xml_object_range + { + public: + typedef It const_iterator; + typedef It iterator; + + xml_object_range(It b, It e): _begin(b), _end(e) + { + } + + It begin() const { return _begin; } + It end() const { return _end; } + + private: + It _begin, _end; + }; + + // Writer interface for node printing (see xml_node::print) + class PUGIXML_CLASS xml_writer + { + public: + virtual ~xml_writer() {} + + // Write memory chunk into stream/file/whatever + virtual void write(const void* data, size_t size) = 0; + }; + + // xml_writer implementation for FILE* + class PUGIXML_CLASS xml_writer_file: public xml_writer + { + public: + // Construct writer from a FILE* object; void* is used to avoid header dependencies on stdio + xml_writer_file(void* file); + + virtual void write(const void* data, size_t size); + + private: + void* file; + }; + + #ifndef PUGIXML_NO_STL + // xml_writer implementation for streams + class PUGIXML_CLASS xml_writer_stream: public xml_writer + { + public: + // Construct writer from an output stream object + xml_writer_stream(std::basic_ostream >& stream); + xml_writer_stream(std::basic_ostream >& stream); + + virtual void write(const void* data, size_t size); + + private: + std::basic_ostream >* narrow_stream; + std::basic_ostream >* wide_stream; + }; + #endif + + // A light-weight handle for manipulating attributes in DOM tree + class PUGIXML_CLASS xml_attribute + { + friend class xml_attribute_iterator; + friend class xml_node; + + private: + xml_attribute_struct* _attr; + + typedef void (*unspecified_bool_type)(xml_attribute***); + + public: + // Default constructor. Constructs an empty attribute. + xml_attribute(); + + // Constructs attribute from internal pointer + explicit xml_attribute(xml_attribute_struct* attr); + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Comparison operators (compares wrapped attribute pointers) + bool operator==(const xml_attribute& r) const; + bool operator!=(const xml_attribute& r) const; + bool operator<(const xml_attribute& r) const; + bool operator>(const xml_attribute& r) const; + bool operator<=(const xml_attribute& r) const; + bool operator>=(const xml_attribute& r) const; + + // Check if attribute is empty + bool empty() const; + + // Get attribute name/value, or "" if attribute is empty + const char_t* name() const; + const char_t* value() const; + + // Get attribute value, or the default value if attribute is empty + const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; + + // Get attribute value as a number, or the default value if conversion did not succeed or attribute is empty + int as_int(int def = 0) const; + unsigned int as_uint(unsigned int def = 0) const; + double as_double(double def = 0) const; + float as_float(float def = 0) const; + + #ifdef PUGIXML_HAS_LONG_LONG + long long as_llong(long long def = 0) const; + unsigned long long as_ullong(unsigned long long def = 0) const; + #endif + + // Get attribute value as bool (returns true if first character is in '1tTyY' set), or the default value if attribute is empty + bool as_bool(bool def = false) const; + + // Set attribute name/value (returns false if attribute is empty or there is not enough memory) + bool set_name(const char_t* rhs); + bool set_value(const char_t* rhs); + + // Set attribute value with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") + bool set_value(int rhs); + bool set_value(unsigned int rhs); + bool set_value(double rhs); + bool set_value(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + bool set_value(long long rhs); + bool set_value(unsigned long long rhs); + #endif + + // Set attribute value (equivalent to set_value without error checking) + xml_attribute& operator=(const char_t* rhs); + xml_attribute& operator=(int rhs); + xml_attribute& operator=(unsigned int rhs); + xml_attribute& operator=(double rhs); + xml_attribute& operator=(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + xml_attribute& operator=(long long rhs); + xml_attribute& operator=(unsigned long long rhs); + #endif + + // Get next/previous attribute in the attribute list of the parent node + xml_attribute next_attribute() const; + xml_attribute previous_attribute() const; + + // Get hash value (unique for handles to the same object) + size_t hash_value() const; + + // Get internal pointer + xml_attribute_struct* internal_object() const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xml_attribute& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xml_attribute& lhs, bool rhs); +#endif + + // A light-weight handle for manipulating nodes in DOM tree + class PUGIXML_CLASS xml_node + { + friend class xml_attribute_iterator; + friend class xml_node_iterator; + friend class xml_named_node_iterator; + + protected: + xml_node_struct* _root; + + typedef void (*unspecified_bool_type)(xml_node***); + + public: + // Default constructor. Constructs an empty node. + xml_node(); + + // Constructs node from internal pointer + explicit xml_node(xml_node_struct* p); + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Comparison operators (compares wrapped node pointers) + bool operator==(const xml_node& r) const; + bool operator!=(const xml_node& r) const; + bool operator<(const xml_node& r) const; + bool operator>(const xml_node& r) const; + bool operator<=(const xml_node& r) const; + bool operator>=(const xml_node& r) const; + + // Check if node is empty. + bool empty() const; + + // Get node type + xml_node_type type() const; + + // Get node name, or "" if node is empty or it has no name + const char_t* name() const; + + // Get node value, or "" if node is empty or it has no value + // Note: For text node.value() does not return "text"! Use child_value() or text() methods to access text inside nodes. + const char_t* value() const; + + // Get attribute list + xml_attribute first_attribute() const; + xml_attribute last_attribute() const; + + // Get children list + xml_node first_child() const; + xml_node last_child() const; + + // Get next/previous sibling in the children list of the parent node + xml_node next_sibling() const; + xml_node previous_sibling() const; + + // Get parent node + xml_node parent() const; + + // Get root of DOM tree this node belongs to + xml_node root() const; + + // Get text object for the current node + xml_text text() const; + + // Get child, attribute or next/previous sibling with the specified name + xml_node child(const char_t* name) const; + xml_attribute attribute(const char_t* name) const; + xml_node next_sibling(const char_t* name) const; + xml_node previous_sibling(const char_t* name) const; + + // Get child value of current node; that is, value of the first child node of type PCDATA/CDATA + const char_t* child_value() const; + + // Get child value of child with specified name. Equivalent to child(name).child_value(). + const char_t* child_value(const char_t* name) const; + + // Set node name/value (returns false if node is empty, there is not enough memory, or node can not have name/value) + bool set_name(const char_t* rhs); + bool set_value(const char_t* rhs); + + // Add attribute with specified name. Returns added attribute, or empty attribute on errors. + xml_attribute append_attribute(const char_t* name); + xml_attribute prepend_attribute(const char_t* name); + xml_attribute insert_attribute_after(const char_t* name, const xml_attribute& attr); + xml_attribute insert_attribute_before(const char_t* name, const xml_attribute& attr); + + // Add a copy of the specified attribute. Returns added attribute, or empty attribute on errors. + xml_attribute append_copy(const xml_attribute& proto); + xml_attribute prepend_copy(const xml_attribute& proto); + xml_attribute insert_copy_after(const xml_attribute& proto, const xml_attribute& attr); + xml_attribute insert_copy_before(const xml_attribute& proto, const xml_attribute& attr); + + // Add child node with specified type. Returns added node, or empty node on errors. + xml_node append_child(xml_node_type type = node_element); + xml_node prepend_child(xml_node_type type = node_element); + xml_node insert_child_after(xml_node_type type, const xml_node& node); + xml_node insert_child_before(xml_node_type type, const xml_node& node); + + // Add child element with specified name. Returns added node, or empty node on errors. + xml_node append_child(const char_t* name); + xml_node prepend_child(const char_t* name); + xml_node insert_child_after(const char_t* name, const xml_node& node); + xml_node insert_child_before(const char_t* name, const xml_node& node); + + // Add a copy of the specified node as a child. Returns added node, or empty node on errors. + xml_node append_copy(const xml_node& proto); + xml_node prepend_copy(const xml_node& proto); + xml_node insert_copy_after(const xml_node& proto, const xml_node& node); + xml_node insert_copy_before(const xml_node& proto, const xml_node& node); + + // Remove specified attribute + bool remove_attribute(const xml_attribute& a); + bool remove_attribute(const char_t* name); + + // Remove specified child + bool remove_child(const xml_node& n); + bool remove_child(const char_t* name); + + // Parses buffer as an XML document fragment and appends all nodes as children of the current node. + // Copies/converts the buffer, so it may be deleted or changed after the function returns. + // Note: append_buffer allocates memory that has the lifetime of the owning document; removing the appended nodes does not immediately reclaim that memory. + xml_parse_result append_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Find attribute using predicate. Returns first attribute for which predicate returned true. + template xml_attribute find_attribute(Predicate pred) const + { + if (!_root) return xml_attribute(); + + for (xml_attribute attrib = first_attribute(); attrib; attrib = attrib.next_attribute()) + if (pred(attrib)) + return attrib; + + return xml_attribute(); + } + + // Find child node using predicate. Returns first child for which predicate returned true. + template xml_node find_child(Predicate pred) const + { + if (!_root) return xml_node(); + + for (xml_node node = first_child(); node; node = node.next_sibling()) + if (pred(node)) + return node; + + return xml_node(); + } + + // Find node from subtree using predicate. Returns first node from subtree (depth-first), for which predicate returned true. + template xml_node find_node(Predicate pred) const + { + if (!_root) return xml_node(); + + xml_node cur = first_child(); + + while (cur._root && cur._root != _root) + { + if (pred(cur)) return cur; + + if (cur.first_child()) cur = cur.first_child(); + else if (cur.next_sibling()) cur = cur.next_sibling(); + else + { + while (!cur.next_sibling() && cur._root != _root) cur = cur.parent(); + + if (cur._root != _root) cur = cur.next_sibling(); + } + } + + return xml_node(); + } + + // Find child node by attribute name/value + xml_node find_child_by_attribute(const char_t* name, const char_t* attr_name, const char_t* attr_value) const; + xml_node find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const; + + #ifndef PUGIXML_NO_STL + // Get the absolute node path from root as a text string. + string_t path(char_t delimiter = '/') const; + #endif + + // Search for a node by path consisting of node names and . or .. elements. + xml_node first_element_by_path(const char_t* path, char_t delimiter = '/') const; + + // Recursively traverse subtree with xml_tree_walker + bool traverse(xml_tree_walker& walker); + + #ifndef PUGIXML_NO_XPATH + // Select single node by evaluating XPath query. Returns first node from the resulting node set. + xpath_node select_single_node(const char_t* query, xpath_variable_set* variables = 0) const; + xpath_node select_single_node(const xpath_query& query) const; + + // Select node set by evaluating XPath query + xpath_node_set select_nodes(const char_t* query, xpath_variable_set* variables = 0) const; + xpath_node_set select_nodes(const xpath_query& query) const; + #endif + + // Print subtree using a writer object + void print(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; + + #ifndef PUGIXML_NO_STL + // Print subtree to stream + void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; + void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, unsigned int depth = 0) const; + #endif + + // Child nodes iterators + typedef xml_node_iterator iterator; + + iterator begin() const; + iterator end() const; + + // Attribute iterators + typedef xml_attribute_iterator attribute_iterator; + + attribute_iterator attributes_begin() const; + attribute_iterator attributes_end() const; + + // Range-based for support + xml_object_range children() const; + xml_object_range children(const char_t* name) const; + xml_object_range attributes() const; + + // Get node offset in parsed file/string (in char_t units) for debugging purposes + ptrdiff_t offset_debug() const; + + // Get hash value (unique for handles to the same object) + size_t hash_value() const; + + // Get internal pointer + xml_node_struct* internal_object() const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xml_node& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xml_node& lhs, bool rhs); +#endif + + // A helper for working with text inside PCDATA nodes + class PUGIXML_CLASS xml_text + { + friend class xml_node; + + xml_node_struct* _root; + + typedef void (*unspecified_bool_type)(xml_text***); + + explicit xml_text(xml_node_struct* root); + + xml_node_struct* _data_new(); + xml_node_struct* _data() const; + + public: + // Default constructor. Constructs an empty object. + xml_text(); + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Check if text object is empty + bool empty() const; + + // Get text, or "" if object is empty + const char_t* get() const; + + // Get text, or the default value if object is empty + const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; + + // Get text as a number, or the default value if conversion did not succeed or object is empty + int as_int(int def = 0) const; + unsigned int as_uint(unsigned int def = 0) const; + double as_double(double def = 0) const; + float as_float(float def = 0) const; + + #ifdef PUGIXML_HAS_LONG_LONG + long long as_llong(long long def = 0) const; + unsigned long long as_ullong(unsigned long long def = 0) const; + #endif + + // Get text as bool (returns true if first character is in '1tTyY' set), or the default value if object is empty + bool as_bool(bool def = false) const; + + // Set text (returns false if object is empty or there is not enough memory) + bool set(const char_t* rhs); + + // Set text with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") + bool set(int rhs); + bool set(unsigned int rhs); + bool set(double rhs); + bool set(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + bool set(long long rhs); + bool set(unsigned long long rhs); + #endif + + // Set text (equivalent to set without error checking) + xml_text& operator=(const char_t* rhs); + xml_text& operator=(int rhs); + xml_text& operator=(unsigned int rhs); + xml_text& operator=(double rhs); + xml_text& operator=(bool rhs); + + #ifdef PUGIXML_HAS_LONG_LONG + xml_text& operator=(long long rhs); + xml_text& operator=(unsigned long long rhs); + #endif + + // Get the data node (node_pcdata or node_cdata) for this object + xml_node data() const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xml_text& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xml_text& lhs, bool rhs); +#endif + + // Child node iterator (a bidirectional iterator over a collection of xml_node) + class PUGIXML_CLASS xml_node_iterator + { + friend class xml_node; + + private: + mutable xml_node _wrap; + xml_node _parent; + + xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent); + + public: + // Iterator traits + typedef ptrdiff_t difference_type; + typedef xml_node value_type; + typedef xml_node* pointer; + typedef xml_node& reference; + + #ifndef PUGIXML_NO_STL + typedef std::bidirectional_iterator_tag iterator_category; + #endif + + // Default constructor + xml_node_iterator(); + + // Construct an iterator which points to the specified node + xml_node_iterator(const xml_node& node); + + // Iterator operators + bool operator==(const xml_node_iterator& rhs) const; + bool operator!=(const xml_node_iterator& rhs) const; + + xml_node& operator*() const; + xml_node* operator->() const; + + const xml_node_iterator& operator++(); + xml_node_iterator operator++(int); + + const xml_node_iterator& operator--(); + xml_node_iterator operator--(int); + }; + + // Attribute iterator (a bidirectional iterator over a collection of xml_attribute) + class PUGIXML_CLASS xml_attribute_iterator + { + friend class xml_node; + + private: + mutable xml_attribute _wrap; + xml_node _parent; + + xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent); + + public: + // Iterator traits + typedef ptrdiff_t difference_type; + typedef xml_attribute value_type; + typedef xml_attribute* pointer; + typedef xml_attribute& reference; + + #ifndef PUGIXML_NO_STL + typedef std::bidirectional_iterator_tag iterator_category; + #endif + + // Default constructor + xml_attribute_iterator(); + + // Construct an iterator which points to the specified attribute + xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent); + + // Iterator operators + bool operator==(const xml_attribute_iterator& rhs) const; + bool operator!=(const xml_attribute_iterator& rhs) const; + + xml_attribute& operator*() const; + xml_attribute* operator->() const; + + const xml_attribute_iterator& operator++(); + xml_attribute_iterator operator++(int); + + const xml_attribute_iterator& operator--(); + xml_attribute_iterator operator--(int); + }; + + // Named node range helper + class PUGIXML_CLASS xml_named_node_iterator + { + friend class xml_node; + + public: + // Iterator traits + typedef ptrdiff_t difference_type; + typedef xml_node value_type; + typedef xml_node* pointer; + typedef xml_node& reference; + + #ifndef PUGIXML_NO_STL + typedef std::bidirectional_iterator_tag iterator_category; + #endif + + // Default constructor + xml_named_node_iterator(); + + // Construct an iterator which points to the specified node + xml_named_node_iterator(const xml_node& node, const char_t* name); + + // Iterator operators + bool operator==(const xml_named_node_iterator& rhs) const; + bool operator!=(const xml_named_node_iterator& rhs) const; + + xml_node& operator*() const; + xml_node* operator->() const; + + const xml_named_node_iterator& operator++(); + xml_named_node_iterator operator++(int); + + const xml_named_node_iterator& operator--(); + xml_named_node_iterator operator--(int); + + private: + mutable xml_node _wrap; + xml_node _parent; + const char_t* _name; + + xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name); + }; + + // Abstract tree walker class (see xml_node::traverse) + class PUGIXML_CLASS xml_tree_walker + { + friend class xml_node; + + private: + int _depth; + + protected: + // Get current traversal depth + int depth() const; + + public: + xml_tree_walker(); + virtual ~xml_tree_walker(); + + // Callback that is called when traversal begins + virtual bool begin(xml_node& node); + + // Callback that is called for each node traversed + virtual bool for_each(xml_node& node) = 0; + + // Callback that is called when traversal ends + virtual bool end(xml_node& node); + }; + + // Parsing status, returned as part of xml_parse_result object + enum xml_parse_status + { + status_ok = 0, // No error + + status_file_not_found, // File was not found during load_file() + status_io_error, // Error reading from file/stream + status_out_of_memory, // Could not allocate memory + status_internal_error, // Internal error occurred + + status_unrecognized_tag, // Parser could not determine tag type + + status_bad_pi, // Parsing error occurred while parsing document declaration/processing instruction + status_bad_comment, // Parsing error occurred while parsing comment + status_bad_cdata, // Parsing error occurred while parsing CDATA section + status_bad_doctype, // Parsing error occurred while parsing document type declaration + status_bad_pcdata, // Parsing error occurred while parsing PCDATA section + status_bad_start_element, // Parsing error occurred while parsing start element tag + status_bad_attribute, // Parsing error occurred while parsing element attribute + status_bad_end_element, // Parsing error occurred while parsing end element tag + status_end_element_mismatch,// There was a mismatch of start-end tags (closing tag had incorrect name, some tag was not closed or there was an excessive closing tag) + + status_append_invalid_root, // Unable to append nodes since root type is not node_element or node_document (exclusive to xml_node::append_buffer) + + status_no_document_element // Parsing resulted in a document without element nodes + }; + + // Parsing result + struct PUGIXML_CLASS xml_parse_result + { + // Parsing status (see xml_parse_status) + xml_parse_status status; + + // Last parsed offset (in char_t units from start of input data) + ptrdiff_t offset; + + // Source document encoding + xml_encoding encoding; + + // Default constructor, initializes object to failed state + xml_parse_result(); + + // Cast to bool operator + operator bool() const; + + // Get error description + const char* description() const; + }; + + // Document class (DOM tree root) + class PUGIXML_CLASS xml_document: public xml_node + { + private: + char_t* _buffer; + + char _memory[192]; + + // Non-copyable semantics + xml_document(const xml_document&); + const xml_document& operator=(const xml_document&); + + void create(); + void destroy(); + + public: + // Default constructor, makes empty document + xml_document(); + + // Destructor, invalidates all node/attribute handles to this document + ~xml_document(); + + // Removes all nodes, leaving the empty document + void reset(); + + // Removes all nodes, then copies the entire contents of the specified document + void reset(const xml_document& proto); + + #ifndef PUGIXML_NO_STL + // Load document from stream. + xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default); + #endif + + // Load document from zero-terminated string. No encoding conversions are applied. + xml_parse_result load(const char_t* contents, unsigned int options = parse_default); + + // Load document from file + xml_parse_result load_file(const char* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + xml_parse_result load_file(const wchar_t* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Load document from buffer. Copies/converts the buffer, so it may be deleted or changed after the function returns. + xml_parse_result load_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). + // You should ensure that buffer data will persist throughout the document's lifetime, and free the buffer memory manually once document is destroyed. + xml_parse_result load_buffer_inplace(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). + // You should allocate the buffer with pugixml allocation function; document will free the buffer when it is no longer needed (you can't use it anymore). + xml_parse_result load_buffer_inplace_own(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); + + // Save XML document to writer (semantics is slightly different from xml_node::print, see documentation for details). + void save(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + + #ifndef PUGIXML_NO_STL + // Save XML document to stream (semantics is slightly different from xml_node::print, see documentation for details). + void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default) const; + #endif + + // Save XML to file + bool save_file(const char* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + bool save_file(const wchar_t* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; + + // Get document element + xml_node document_element() const; + }; + +#ifndef PUGIXML_NO_XPATH + // XPath query return type + enum xpath_value_type + { + xpath_type_none, // Unknown type (query failed to compile) + xpath_type_node_set, // Node set (xpath_node_set) + xpath_type_number, // Number + xpath_type_string, // String + xpath_type_boolean // Boolean + }; + + // XPath parsing result + struct PUGIXML_CLASS xpath_parse_result + { + // Error message (0 if no error) + const char* error; + + // Last parsed offset (in char_t units from string start) + ptrdiff_t offset; + + // Default constructor, initializes object to failed state + xpath_parse_result(); + + // Cast to bool operator + operator bool() const; + + // Get error description + const char* description() const; + }; + + // A single XPath variable + class PUGIXML_CLASS xpath_variable + { + friend class xpath_variable_set; + + protected: + xpath_value_type _type; + xpath_variable* _next; + + xpath_variable(); + + // Non-copyable semantics + xpath_variable(const xpath_variable&); + xpath_variable& operator=(const xpath_variable&); + + public: + // Get variable name + const char_t* name() const; + + // Get variable type + xpath_value_type type() const; + + // Get variable value; no type conversion is performed, default value (false, NaN, empty string, empty node set) is returned on type mismatch error + bool get_boolean() const; + double get_number() const; + const char_t* get_string() const; + const xpath_node_set& get_node_set() const; + + // Set variable value; no type conversion is performed, false is returned on type mismatch error + bool set(bool value); + bool set(double value); + bool set(const char_t* value); + bool set(const xpath_node_set& value); + }; + + // A set of XPath variables + class PUGIXML_CLASS xpath_variable_set + { + private: + xpath_variable* _data[64]; + + // Non-copyable semantics + xpath_variable_set(const xpath_variable_set&); + xpath_variable_set& operator=(const xpath_variable_set&); + + xpath_variable* find(const char_t* name) const; + + public: + // Default constructor/destructor + xpath_variable_set(); + ~xpath_variable_set(); + + // Add a new variable or get the existing one, if the types match + xpath_variable* add(const char_t* name, xpath_value_type type); + + // Set value of an existing variable; no type conversion is performed, false is returned if there is no such variable or if types mismatch + bool set(const char_t* name, bool value); + bool set(const char_t* name, double value); + bool set(const char_t* name, const char_t* value); + bool set(const char_t* name, const xpath_node_set& value); + + // Get existing variable by name + xpath_variable* get(const char_t* name); + const xpath_variable* get(const char_t* name) const; + }; + + // A compiled XPath query object + class PUGIXML_CLASS xpath_query + { + private: + void* _impl; + xpath_parse_result _result; + + typedef void (*unspecified_bool_type)(xpath_query***); + + // Non-copyable semantics + xpath_query(const xpath_query&); + xpath_query& operator=(const xpath_query&); + + public: + // Construct a compiled object from XPath expression. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on compilation errors. + explicit xpath_query(const char_t* query, xpath_variable_set* variables = 0); + + // Destructor + ~xpath_query(); + + // Get query expression return type + xpath_value_type return_type() const; + + // Evaluate expression as boolean value in the specified context; performs type conversion if necessary. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + bool evaluate_boolean(const xpath_node& n) const; + + // Evaluate expression as double value in the specified context; performs type conversion if necessary. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + double evaluate_number(const xpath_node& n) const; + + #ifndef PUGIXML_NO_STL + // Evaluate expression as string value in the specified context; performs type conversion if necessary. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + string_t evaluate_string(const xpath_node& n) const; + #endif + + // Evaluate expression as string value in the specified context; performs type conversion if necessary. + // At most capacity characters are written to the destination buffer, full result size is returned (includes terminating zero). + // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. + // If PUGIXML_NO_EXCEPTIONS is defined, returns empty set instead. + size_t evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const; + + // Evaluate expression as node set in the specified context. + // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors. + // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node set instead. + xpath_node_set evaluate_node_set(const xpath_node& n) const; + + // Get parsing result (used to get compilation errors in PUGIXML_NO_EXCEPTIONS mode) + const xpath_parse_result& result() const; + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + }; + + #ifndef PUGIXML_NO_EXCEPTIONS + // XPath exception class + class PUGIXML_CLASS xpath_exception: public std::exception + { + private: + xpath_parse_result _result; + + public: + // Construct exception from parse result + explicit xpath_exception(const xpath_parse_result& result); + + // Get error message + virtual const char* what() const throw(); + + // Get parse result + const xpath_parse_result& result() const; + }; + #endif + + // XPath node class (either xml_node or xml_attribute) + class PUGIXML_CLASS xpath_node + { + private: + xml_node _node; + xml_attribute _attribute; + + typedef void (*unspecified_bool_type)(xpath_node***); + + public: + // Default constructor; constructs empty XPath node + xpath_node(); + + // Construct XPath node from XML node/attribute + xpath_node(const xml_node& node); + xpath_node(const xml_attribute& attribute, const xml_node& parent); + + // Get node/attribute, if any + xml_node node() const; + xml_attribute attribute() const; + + // Get parent of contained node/attribute + xml_node parent() const; + + // Safe bool conversion operator + operator unspecified_bool_type() const; + + // Borland C++ workaround + bool operator!() const; + + // Comparison operators + bool operator==(const xpath_node& n) const; + bool operator!=(const xpath_node& n) const; + }; + +#ifdef __BORLANDC__ + // Borland C++ workaround + bool PUGIXML_FUNCTION operator&&(const xpath_node& lhs, bool rhs); + bool PUGIXML_FUNCTION operator||(const xpath_node& lhs, bool rhs); +#endif + + // A fixed-size collection of XPath nodes + class PUGIXML_CLASS xpath_node_set + { + public: + // Collection type + enum type_t + { + type_unsorted, // Not ordered + type_sorted, // Sorted by document order (ascending) + type_sorted_reverse // Sorted by document order (descending) + }; + + // Constant iterator type + typedef const xpath_node* const_iterator; + + // Default constructor. Constructs empty set. + xpath_node_set(); + + // Constructs a set from iterator range; data is not checked for duplicates and is not sorted according to provided type, so be careful + xpath_node_set(const_iterator begin, const_iterator end, type_t type = type_unsorted); + + // Destructor + ~xpath_node_set(); + + // Copy constructor/assignment operator + xpath_node_set(const xpath_node_set& ns); + xpath_node_set& operator=(const xpath_node_set& ns); + + // Get collection type + type_t type() const; + + // Get collection size + size_t size() const; + + // Indexing operator + const xpath_node& operator[](size_t index) const; + + // Collection iterators + const_iterator begin() const; + const_iterator end() const; + + // Sort the collection in ascending/descending order by document order + void sort(bool reverse = false); + + // Get first node in the collection by document order + xpath_node first() const; + + // Check if collection is empty + bool empty() const; + + private: + type_t _type; + + xpath_node _storage; + + xpath_node* _begin; + xpath_node* _end; + + void _assign(const_iterator begin, const_iterator end); + }; +#endif + +#ifndef PUGIXML_NO_STL + // Convert wide string to UTF8 + std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const wchar_t* str); + std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const std::basic_string, std::allocator >& str); + + // Convert UTF8 to wide string + std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const char* str); + std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const std::basic_string, std::allocator >& str); +#endif + + // Memory allocation function interface; returns pointer to allocated memory or NULL on failure + typedef void* (*allocation_function)(size_t size); + + // Memory deallocation function interface + typedef void (*deallocation_function)(void* ptr); + + // Override default memory management functions. All subsequent allocations/deallocations will be performed via supplied functions. + void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate); + + // Get current memory management functions + allocation_function PUGIXML_FUNCTION get_memory_allocation_function(); + deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function(); +} + +#if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) +namespace std +{ + // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) + std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_node_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_attribute_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_named_node_iterator&); +} +#endif + +#if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) +namespace std +{ + // Workarounds for (non-standard) iterator category detection + std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_node_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_attribute_iterator&); + std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_named_node_iterator&); +} +#endif + +#endif + +/** + * Copyright (c) 2006-2014 Arseny Kapoulkine + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ diff --git a/third_party/zlib/adler32.c b/deps/src/zlib/adler32.c similarity index 73% rename from third_party/zlib/adler32.c rename to deps/src/zlib/adler32.c index 65ad6a5ad..a868f073d 100644 --- a/third_party/zlib/adler32.c +++ b/deps/src/zlib/adler32.c @@ -1,5 +1,5 @@ /* adler32.c -- compute the Adler-32 checksum of a data stream - * Copyright (C) 1995-2007 Mark Adler + * Copyright (C) 1995-2011 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -9,9 +9,9 @@ #define local static -local uLong adler32_combine_(uLong adler1, uLong adler2, z_off64_t len2); +local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2)); -#define BASE 65521UL /* largest prime smaller than 65536 */ +#define BASE 65521 /* largest prime smaller than 65536 */ #define NMAX 5552 /* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ @@ -21,39 +21,44 @@ local uLong adler32_combine_(uLong adler1, uLong adler2, z_off64_t len2); #define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); #define DO16(buf) DO8(buf,0); DO8(buf,8); -/* use NO_DIVIDE if your processor does not do division in hardware */ +/* use NO_DIVIDE if your processor does not do division in hardware -- + try it both ways to see which is faster */ #ifdef NO_DIVIDE -# define MOD(a) \ +/* note that this assumes BASE is 65521, where 65536 % 65521 == 15 + (thank you to John Reiser for pointing this out) */ +# define CHOP(a) \ + do { \ + unsigned long tmp = a >> 16; \ + a &= 0xffffUL; \ + a += (tmp << 4) - tmp; \ + } while (0) +# define MOD28(a) \ do { \ - if (a >= (BASE << 16)) a -= (BASE << 16); \ - if (a >= (BASE << 15)) a -= (BASE << 15); \ - if (a >= (BASE << 14)) a -= (BASE << 14); \ - if (a >= (BASE << 13)) a -= (BASE << 13); \ - if (a >= (BASE << 12)) a -= (BASE << 12); \ - if (a >= (BASE << 11)) a -= (BASE << 11); \ - if (a >= (BASE << 10)) a -= (BASE << 10); \ - if (a >= (BASE << 9)) a -= (BASE << 9); \ - if (a >= (BASE << 8)) a -= (BASE << 8); \ - if (a >= (BASE << 7)) a -= (BASE << 7); \ - if (a >= (BASE << 6)) a -= (BASE << 6); \ - if (a >= (BASE << 5)) a -= (BASE << 5); \ - if (a >= (BASE << 4)) a -= (BASE << 4); \ - if (a >= (BASE << 3)) a -= (BASE << 3); \ - if (a >= (BASE << 2)) a -= (BASE << 2); \ - if (a >= (BASE << 1)) a -= (BASE << 1); \ + CHOP(a); \ if (a >= BASE) a -= BASE; \ } while (0) -# define MOD4(a) \ +# define MOD(a) \ do { \ - if (a >= (BASE << 4)) a -= (BASE << 4); \ - if (a >= (BASE << 3)) a -= (BASE << 3); \ - if (a >= (BASE << 2)) a -= (BASE << 2); \ - if (a >= (BASE << 1)) a -= (BASE << 1); \ + CHOP(a); \ + MOD28(a); \ + } while (0) +# define MOD63(a) \ + do { /* this assumes a is not negative */ \ + z_off64_t tmp = a >> 32; \ + a &= 0xffffffffL; \ + a += (tmp << 8) - (tmp << 5) + tmp; \ + tmp = a >> 16; \ + a &= 0xffffL; \ + a += (tmp << 4) - tmp; \ + tmp = a >> 16; \ + a &= 0xffffL; \ + a += (tmp << 4) - tmp; \ if (a >= BASE) a -= BASE; \ } while (0) #else # define MOD(a) a %= BASE -# define MOD4(a) a %= BASE +# define MOD28(a) a %= BASE +# define MOD63(a) a %= BASE #endif /* ========================================================================= */ @@ -92,7 +97,7 @@ uLong ZEXPORT adler32(adler, buf, len) } if (adler >= BASE) adler -= BASE; - MOD4(sum2); /* only added so many BASE's */ + MOD28(sum2); /* only added so many BASE's */ return adler | (sum2 << 16); } @@ -137,8 +142,13 @@ local uLong adler32_combine_(adler1, adler2, len2) unsigned long sum2; unsigned rem; + /* for negative len, return invalid adler32 as a clue for debugging */ + if (len2 < 0) + return 0xffffffffUL; + /* the derivation of this formula is left as an exercise for the reader */ - rem = (unsigned)(len2 % BASE); + MOD63(len2); /* assumes len2 >= 0 */ + rem = (unsigned)len2; sum1 = adler1 & 0xffff; sum2 = rem * sum1; MOD(sum2); diff --git a/third_party/zlib/compress.c b/deps/src/zlib/compress.c similarity index 98% rename from third_party/zlib/compress.c rename to deps/src/zlib/compress.c index ea4dfbe9d..6e9762676 100644 --- a/third_party/zlib/compress.c +++ b/deps/src/zlib/compress.c @@ -29,7 +29,7 @@ int ZEXPORT compress2 (dest, destLen, source, sourceLen, level) z_stream stream; int err; - stream.next_in = (Bytef*)source; + stream.next_in = (z_const Bytef *)source; stream.avail_in = (uInt)sourceLen; #ifdef MAXSEG_64K /* Check for source > 64K on 16-bit machine: */ diff --git a/third_party/zlib/crc32.c b/deps/src/zlib/crc32.c similarity index 86% rename from third_party/zlib/crc32.c rename to deps/src/zlib/crc32.c index 91be372d2..979a7190a 100644 --- a/third_party/zlib/crc32.c +++ b/deps/src/zlib/crc32.c @@ -1,5 +1,5 @@ /* crc32.c -- compute the CRC-32 of a data stream - * Copyright (C) 1995-2006, 2010 Mark Adler + * Copyright (C) 1995-2006, 2010, 2011, 2012 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h * * Thanks to Rodney Brown for his contribution of faster @@ -17,6 +17,8 @@ of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should first call get_crc_table() to initialize the tables before allowing more than one thread to use crc32(). + + DYNAMIC_CRC_TABLE and MAKECRCH can be #defined to write out crc32.h. */ #ifdef MAKECRCH @@ -30,31 +32,11 @@ #define local static -/* Find a four-byte integer type for crc32_little() and crc32_big(). */ -#ifndef NOBYFOUR -# ifdef STDC /* need ANSI C limits.h to determine sizes */ -# include -# define BYFOUR -# if (UINT_MAX == 0xffffffffUL) - typedef unsigned int u4; -# else -# if (ULONG_MAX == 0xffffffffUL) - typedef unsigned long u4; -# else -# if (USHRT_MAX == 0xffffffffUL) - typedef unsigned short u4; -# else -# undef BYFOUR /* can't find a four-byte integer type! */ -# endif -# endif -# endif -# endif /* STDC */ -#endif /* !NOBYFOUR */ - /* Definitions for doing the crc four data bytes at a time. */ +#if !defined(NOBYFOUR) && defined(Z_U4) +# define BYFOUR +#endif #ifdef BYFOUR -# define REV(w) ((((w)>>24)&0xff)+(((w)>>8)&0xff00)+ \ - (((w)&0xff00)<<8)+(((w)&0xff)<<24)) local unsigned long crc32_little OF((unsigned long, const unsigned char FAR *, unsigned)); local unsigned long crc32_big OF((unsigned long, @@ -68,16 +50,16 @@ local unsigned long gf2_matrix_times OF((unsigned long *mat, unsigned long vec)); local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat)); -local uLong crc32_combine_(uLong crc1, uLong crc2, z_off64_t len2); +local uLong crc32_combine_ OF((uLong crc1, uLong crc2, z_off64_t len2)); #ifdef DYNAMIC_CRC_TABLE local volatile int crc_table_empty = 1; -local unsigned long FAR crc_table[TBLS][256]; +local z_crc_t FAR crc_table[TBLS][256]; local void make_crc_table OF((void)); #ifdef MAKECRCH - local void write_table OF((FILE *, const unsigned long FAR *)); + local void write_table OF((FILE *, const z_crc_t FAR *)); #endif /* MAKECRCH */ /* Generate tables for a byte-wise 32-bit CRC calculation on the polynomial: @@ -107,9 +89,9 @@ local void make_crc_table OF((void)); */ local void make_crc_table() { - unsigned long c; + z_crc_t c; int n, k; - unsigned long poly; /* polynomial exclusive-or pattern */ + z_crc_t poly; /* polynomial exclusive-or pattern */ /* terms of polynomial defining this crc (except x^32): */ static volatile int first = 1; /* flag to limit concurrent making */ static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; @@ -121,13 +103,13 @@ local void make_crc_table() first = 0; /* make exclusive-or pattern from polynomial (0xedb88320UL) */ - poly = 0UL; - for (n = 0; n < sizeof(p)/sizeof(unsigned char); n++) - poly |= 1UL << (31 - p[n]); + poly = 0; + for (n = 0; n < (int)(sizeof(p)/sizeof(unsigned char)); n++) + poly |= (z_crc_t)1 << (31 - p[n]); /* generate a crc for every 8-bit value */ for (n = 0; n < 256; n++) { - c = (unsigned long)n; + c = (z_crc_t)n; for (k = 0; k < 8; k++) c = c & 1 ? poly ^ (c >> 1) : c >> 1; crc_table[0][n] = c; @@ -138,11 +120,11 @@ local void make_crc_table() and then the byte reversal of those as well as the first table */ for (n = 0; n < 256; n++) { c = crc_table[0][n]; - crc_table[4][n] = REV(c); + crc_table[4][n] = ZSWAP32(c); for (k = 1; k < 4; k++) { c = crc_table[0][c & 0xff] ^ (c >> 8); crc_table[k][n] = c; - crc_table[k + 4][n] = REV(c); + crc_table[k + 4][n] = ZSWAP32(c); } } #endif /* BYFOUR */ @@ -164,7 +146,7 @@ local void make_crc_table() if (out == NULL) return; fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n"); fprintf(out, " * Generated automatically by crc32.c\n */\n\n"); - fprintf(out, "local const unsigned long FAR "); + fprintf(out, "local const z_crc_t FAR "); fprintf(out, "crc_table[TBLS][256] =\n{\n {\n"); write_table(out, crc_table[0]); # ifdef BYFOUR @@ -184,12 +166,13 @@ local void make_crc_table() #ifdef MAKECRCH local void write_table(out, table) FILE *out; - const unsigned long FAR *table; + const z_crc_t FAR *table; { int n; for (n = 0; n < 256; n++) - fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ", table[n], + fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ", + (unsigned long)(table[n]), n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", ")); } #endif /* MAKECRCH */ @@ -204,13 +187,13 @@ local void write_table(out, table) /* ========================================================================= * This function can be used by asm versions of crc32() */ -const unsigned long FAR * ZEXPORT get_crc_table() +const z_crc_t FAR * ZEXPORT get_crc_table() { #ifdef DYNAMIC_CRC_TABLE if (crc_table_empty) make_crc_table(); #endif /* DYNAMIC_CRC_TABLE */ - return (const unsigned long FAR *)crc_table; + return (const z_crc_t FAR *)crc_table; } /* ========================================================================= */ @@ -232,7 +215,7 @@ unsigned long ZEXPORT crc32(crc, buf, len) #ifdef BYFOUR if (sizeof(void *) == sizeof(ptrdiff_t)) { - u4 endian; + z_crc_t endian; endian = 1; if (*((unsigned char *)(&endian))) @@ -266,17 +249,17 @@ local unsigned long crc32_little(crc, buf, len) const unsigned char FAR *buf; unsigned len; { - register u4 c; - register const u4 FAR *buf4; + register z_crc_t c; + register const z_crc_t FAR *buf4; - c = (u4)crc; + c = (z_crc_t)crc; c = ~c; while (len && ((ptrdiff_t)buf & 3)) { c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); len--; } - buf4 = (const u4 FAR *)(const void FAR *)buf; + buf4 = (const z_crc_t FAR *)(const void FAR *)buf; while (len >= 32) { DOLIT32; len -= 32; @@ -306,17 +289,17 @@ local unsigned long crc32_big(crc, buf, len) const unsigned char FAR *buf; unsigned len; { - register u4 c; - register const u4 FAR *buf4; + register z_crc_t c; + register const z_crc_t FAR *buf4; - c = REV((u4)crc); + c = ZSWAP32((z_crc_t)crc); c = ~c; while (len && ((ptrdiff_t)buf & 3)) { c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); len--; } - buf4 = (const u4 FAR *)(const void FAR *)buf; + buf4 = (const z_crc_t FAR *)(const void FAR *)buf; buf4--; while (len >= 32) { DOBIG32; @@ -333,7 +316,7 @@ local unsigned long crc32_big(crc, buf, len) c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); } while (--len); c = ~c; - return (unsigned long)(REV(c)); + return (unsigned long)(ZSWAP32(c)); } #endif /* BYFOUR */ diff --git a/third_party/zlib/crc32.h b/deps/src/zlib/crc32.h similarity index 99% rename from third_party/zlib/crc32.h rename to deps/src/zlib/crc32.h index 8053b6117..9e0c77810 100644 --- a/third_party/zlib/crc32.h +++ b/deps/src/zlib/crc32.h @@ -2,7 +2,7 @@ * Generated automatically by crc32.c */ -local const unsigned long FAR crc_table[TBLS][256] = +local const z_crc_t FAR crc_table[TBLS][256] = { { 0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL, diff --git a/third_party/zlib/deflate.c b/deps/src/zlib/deflate.c similarity index 91% rename from third_party/zlib/deflate.c rename to deps/src/zlib/deflate.c index 5c4022f3d..696957705 100644 --- a/third_party/zlib/deflate.c +++ b/deps/src/zlib/deflate.c @@ -1,5 +1,5 @@ /* deflate.c -- compress data using the deflation algorithm - * Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler + * Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -37,7 +37,7 @@ * REFERENCES * * Deutsch, L.P.,"DEFLATE Compressed Data Format Specification". - * Available in http://www.ietf.org/rfc/rfc1951.txt + * Available in http://tools.ietf.org/html/rfc1951 * * A description of the Rabin and Karp algorithm is given in the book * "Algorithms" by R. Sedgewick, Addison-Wesley, p252. @@ -52,7 +52,7 @@ #include "deflate.h" const char deflate_copyright[] = - " deflate 1.2.5 Copyright 1995-2010 Jean-loup Gailly and Mark Adler "; + " deflate 1.2.8 Copyright 1995-2013 Jean-loup Gailly and Mark Adler "; /* If you use the zlib library in a product, an acknowledgment is welcome in the documentation of your product. If for some reason you cannot @@ -155,6 +155,9 @@ local const config configuration_table[10] = { struct static_tree_desc_s {int dummy;}; /* for buggy compilers */ #endif +/* rank Z_BLOCK between Z_NO_FLUSH and Z_PARTIAL_FLUSH */ +#define RANK(f) (((f) << 1) - ((f) > 4 ? 9 : 0)) + /* =========================================================================== * Update a hash value with the given input byte * IN assertion: all calls to to UPDATE_HASH are made with consecutive @@ -235,10 +238,19 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, strm->msg = Z_NULL; if (strm->zalloc == (alloc_func)0) { +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else strm->zalloc = zcalloc; strm->opaque = (voidpf)0; +#endif } - if (strm->zfree == (free_func)0) strm->zfree = zcfree; + if (strm->zfree == (free_func)0) +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zfree = zcfree; +#endif #ifdef FASTEST if (level != 0) level = 1; @@ -293,7 +305,7 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL || s->pending_buf == Z_NULL) { s->status = FINISH_STATE; - strm->msg = (char*)ERR_MSG(Z_MEM_ERROR); + strm->msg = ERR_MSG(Z_MEM_ERROR); deflateEnd (strm); return Z_MEM_ERROR; } @@ -314,43 +326,70 @@ int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength) uInt dictLength; { deflate_state *s; - uInt length = dictLength; - uInt n; - IPos hash_head = 0; + uInt str, n; + int wrap; + unsigned avail; + z_const unsigned char *next; - if (strm == Z_NULL || strm->state == Z_NULL || dictionary == Z_NULL || - strm->state->wrap == 2 || - (strm->state->wrap == 1 && strm->state->status != INIT_STATE)) + if (strm == Z_NULL || strm->state == Z_NULL || dictionary == Z_NULL) return Z_STREAM_ERROR; - s = strm->state; - if (s->wrap) - strm->adler = adler32(strm->adler, dictionary, dictLength); + wrap = s->wrap; + if (wrap == 2 || (wrap == 1 && s->status != INIT_STATE) || s->lookahead) + return Z_STREAM_ERROR; - if (length < MIN_MATCH) return Z_OK; - if (length > s->w_size) { - length = s->w_size; - dictionary += dictLength - length; /* use the tail of the dictionary */ + /* when using zlib wrappers, compute Adler-32 for provided dictionary */ + if (wrap == 1) + strm->adler = adler32(strm->adler, dictionary, dictLength); + s->wrap = 0; /* avoid computing Adler-32 in read_buf */ + + /* if dictionary would fill window, just replace the history */ + if (dictLength >= s->w_size) { + if (wrap == 0) { /* already empty otherwise */ + CLEAR_HASH(s); + s->strstart = 0; + s->block_start = 0L; + s->insert = 0; + } + dictionary += dictLength - s->w_size; /* use the tail */ + dictLength = s->w_size; } - zmemcpy(s->window, dictionary, length); - s->strstart = length; - s->block_start = (long)length; - /* Insert all strings in the hash table (except for the last two bytes). - * s->lookahead stays null, so s->ins_h will be recomputed at the next - * call of fill_window. - */ - s->ins_h = s->window[0]; - UPDATE_HASH(s, s->ins_h, s->window[1]); - for (n = 0; n <= length - MIN_MATCH; n++) { - INSERT_STRING(s, n, hash_head); + /* insert dictionary into window and hash */ + avail = strm->avail_in; + next = strm->next_in; + strm->avail_in = dictLength; + strm->next_in = (z_const Bytef *)dictionary; + fill_window(s); + while (s->lookahead >= MIN_MATCH) { + str = s->strstart; + n = s->lookahead - (MIN_MATCH-1); + do { + UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); +#ifndef FASTEST + s->prev[str & s->w_mask] = s->head[s->ins_h]; +#endif + s->head[s->ins_h] = (Pos)str; + str++; + } while (--n); + s->strstart = str; + s->lookahead = MIN_MATCH-1; + fill_window(s); } - if (hash_head) hash_head = 0; /* to make compiler happy */ + s->strstart += s->lookahead; + s->block_start = (long)s->strstart; + s->insert = s->lookahead; + s->lookahead = 0; + s->match_length = s->prev_length = MIN_MATCH-1; + s->match_available = 0; + strm->next_in = next; + strm->avail_in = avail; + s->wrap = wrap; return Z_OK; } /* ========================================================================= */ -int ZEXPORT deflateReset (strm) +int ZEXPORT deflateResetKeep (strm) z_streamp strm; { deflate_state *s; @@ -380,11 +419,22 @@ int ZEXPORT deflateReset (strm) s->last_flush = Z_NO_FLUSH; _tr_init(s); - lm_init(s); return Z_OK; } +/* ========================================================================= */ +int ZEXPORT deflateReset (strm) + z_streamp strm; +{ + int ret; + + ret = deflateResetKeep(strm); + if (ret == Z_OK) + lm_init(strm->state); + return ret; +} + /* ========================================================================= */ int ZEXPORT deflateSetHeader (strm, head) z_streamp strm; @@ -396,15 +446,43 @@ int ZEXPORT deflateSetHeader (strm, head) return Z_OK; } +/* ========================================================================= */ +int ZEXPORT deflatePending (strm, pending, bits) + unsigned *pending; + int *bits; + z_streamp strm; +{ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + if (pending != Z_NULL) + *pending = strm->state->pending; + if (bits != Z_NULL) + *bits = strm->state->bi_valid; + return Z_OK; +} + /* ========================================================================= */ int ZEXPORT deflatePrime (strm, bits, value) z_streamp strm; int bits; int value; { + deflate_state *s; + int put; + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; - strm->state->bi_valid = bits; - strm->state->bi_buf = (ush)(value & ((1 << bits) - 1)); + s = strm->state; + if ((Bytef *)(s->d_buf) < s->pending_out + ((Buf_size + 7) >> 3)) + return Z_BUF_ERROR; + do { + put = Buf_size - s->bi_valid; + if (put > bits) + put = bits; + s->bi_buf |= (ush)((value & ((1 << put) - 1)) << s->bi_valid); + s->bi_valid += put; + _tr_flush_bits(s); + value >>= put; + bits -= put; + } while (bits); return Z_OK; } @@ -435,6 +513,8 @@ int ZEXPORT deflateParams(strm, level, strategy) strm->total_in != 0) { /* Flush the last buffer: */ err = deflate(strm, Z_BLOCK); + if (err == Z_BUF_ERROR && s->pending == 0) + err = Z_OK; } if (s->level != level) { s->level = level; @@ -562,19 +642,22 @@ local void putShortMSB (s, b) local void flush_pending(strm) z_streamp strm; { - unsigned len = strm->state->pending; + unsigned len; + deflate_state *s = strm->state; + _tr_flush_bits(s); + len = s->pending; if (len > strm->avail_out) len = strm->avail_out; if (len == 0) return; - zmemcpy(strm->next_out, strm->state->pending_out, len); + zmemcpy(strm->next_out, s->pending_out, len); strm->next_out += len; - strm->state->pending_out += len; + s->pending_out += len; strm->total_out += len; strm->avail_out -= len; - strm->state->pending -= len; - if (strm->state->pending == 0) { - strm->state->pending_out = strm->state->pending_buf; + s->pending -= len; + if (s->pending == 0) { + s->pending_out = s->pending_buf; } } @@ -801,7 +884,7 @@ int ZEXPORT deflate (strm, flush) * flushes. For repeated and useless calls with Z_FINISH, we keep * returning Z_STREAM_END instead of Z_BUF_ERROR. */ - } else if (strm->avail_in == 0 && flush <= old_flush && + } else if (strm->avail_in == 0 && RANK(flush) <= RANK(old_flush) && flush != Z_FINISH) { ERR_RETURN(strm, Z_BUF_ERROR); } @@ -850,6 +933,7 @@ int ZEXPORT deflate (strm, flush) if (s->lookahead == 0) { s->strstart = 0; s->block_start = 0L; + s->insert = 0; } } } @@ -945,12 +1029,12 @@ int ZEXPORT deflateCopy (dest, source) ss = source->state; - zmemcpy(dest, source, sizeof(z_stream)); + zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream)); ds = (deflate_state *) ZALLOC(dest, 1, sizeof(deflate_state)); if (ds == Z_NULL) return Z_MEM_ERROR; dest->state = (struct internal_state FAR *) ds; - zmemcpy(ds, ss, sizeof(deflate_state)); + zmemcpy((voidpf)ds, (voidpf)ss, sizeof(deflate_state)); ds->strm = dest; ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte)); @@ -966,8 +1050,8 @@ int ZEXPORT deflateCopy (dest, source) } /* following zmemcpy do not work for 16-bit MSDOS */ zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte)); - zmemcpy(ds->prev, ss->prev, ds->w_size * sizeof(Pos)); - zmemcpy(ds->head, ss->head, ds->hash_size * sizeof(Pos)); + zmemcpy((voidpf)ds->prev, (voidpf)ss->prev, ds->w_size * sizeof(Pos)); + zmemcpy((voidpf)ds->head, (voidpf)ss->head, ds->hash_size * sizeof(Pos)); zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size); ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf); @@ -1001,15 +1085,15 @@ local int read_buf(strm, buf, size) strm->avail_in -= len; + zmemcpy(buf, strm->next_in, len); if (strm->state->wrap == 1) { - strm->adler = adler32(strm->adler, strm->next_in, len); + strm->adler = adler32(strm->adler, buf, len); } #ifdef GZIP else if (strm->state->wrap == 2) { - strm->adler = crc32(strm->adler, strm->next_in, len); + strm->adler = crc32(strm->adler, buf, len); } #endif - zmemcpy(buf, strm->next_in, len); strm->next_in += len; strm->total_in += len; @@ -1036,6 +1120,7 @@ local void lm_init (s) s->strstart = 0; s->block_start = 0L; s->lookahead = 0; + s->insert = 0; s->match_length = s->prev_length = MIN_MATCH-1; s->match_available = 0; s->ins_h = 0; @@ -1310,6 +1395,8 @@ local void fill_window(s) unsigned more; /* Amount of free space at the end of the window. */ uInt wsize = s->w_size; + Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); + do { more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart); @@ -1362,7 +1449,7 @@ local void fill_window(s) #endif more += wsize; } - if (s->strm->avail_in == 0) return; + if (s->strm->avail_in == 0) break; /* If there was no sliding: * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && @@ -1381,12 +1468,24 @@ local void fill_window(s) s->lookahead += n; /* Initialize the hash value now that we have some input: */ - if (s->lookahead >= MIN_MATCH) { - s->ins_h = s->window[s->strstart]; - UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); + if (s->lookahead + s->insert >= MIN_MATCH) { + uInt str = s->strstart - s->insert; + s->ins_h = s->window[str]; + UPDATE_HASH(s, s->ins_h, s->window[str + 1]); #if MIN_MATCH != 3 Call UPDATE_HASH() MIN_MATCH-3 more times #endif + while (s->insert) { + UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); +#ifndef FASTEST + s->prev[str & s->w_mask] = s->head[s->ins_h]; +#endif + s->head[s->ins_h] = (Pos)str; + str++; + s->insert--; + if (s->lookahead + s->insert < MIN_MATCH) + break; + } } /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, * but this is not important since only literal bytes will be emitted. @@ -1427,6 +1526,9 @@ local void fill_window(s) s->high_water += init; } } + + Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, + "not enough room for search"); } /* =========================================================================== @@ -1506,8 +1608,14 @@ local block_state deflate_stored(s, flush) FLUSH_BLOCK(s, 0); } } - FLUSH_BLOCK(s, flush == Z_FINISH); - return flush == Z_FINISH ? finish_done : block_done; + s->insert = 0; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if ((long)s->strstart > s->block_start) + FLUSH_BLOCK(s, 0); + return block_done; } /* =========================================================================== @@ -1603,8 +1711,14 @@ local block_state deflate_fast(s, flush) } if (bflush) FLUSH_BLOCK(s, 0); } - FLUSH_BLOCK(s, flush == Z_FINISH); - return flush == Z_FINISH ? finish_done : block_done; + s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; } #ifndef FASTEST @@ -1728,8 +1842,14 @@ local block_state deflate_slow(s, flush) _tr_tally_lit(s, s->window[s->strstart-1], bflush); s->match_available = 0; } - FLUSH_BLOCK(s, flush == Z_FINISH); - return flush == Z_FINISH ? finish_done : block_done; + s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; } #endif /* FASTEST */ @@ -1749,11 +1869,11 @@ local block_state deflate_rle(s, flush) for (;;) { /* Make sure that we always have enough lookahead, except * at the end of the input file. We need MAX_MATCH bytes - * for the longest encodable run. + * for the longest run, plus one for the unrolled loop. */ - if (s->lookahead < MAX_MATCH) { + if (s->lookahead <= MAX_MATCH) { fill_window(s); - if (s->lookahead < MAX_MATCH && flush == Z_NO_FLUSH) { + if (s->lookahead <= MAX_MATCH && flush == Z_NO_FLUSH) { return need_more; } if (s->lookahead == 0) break; /* flush the current block */ @@ -1776,6 +1896,7 @@ local block_state deflate_rle(s, flush) if (s->match_length > s->lookahead) s->match_length = s->lookahead; } + Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); } /* Emit match if have run of MIN_MATCH or longer, else emit literal */ @@ -1796,8 +1917,14 @@ local block_state deflate_rle(s, flush) } if (bflush) FLUSH_BLOCK(s, 0); } - FLUSH_BLOCK(s, flush == Z_FINISH); - return flush == Z_FINISH ? finish_done : block_done; + s->insert = 0; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; } /* =========================================================================== @@ -1829,6 +1956,12 @@ local block_state deflate_huff(s, flush) s->strstart++; if (bflush) FLUSH_BLOCK(s, 0); } - FLUSH_BLOCK(s, flush == Z_FINISH); - return flush == Z_FINISH ? finish_done : block_done; + s->insert = 0; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; } diff --git a/third_party/zlib/deflate.h b/deps/src/zlib/deflate.h similarity index 97% rename from third_party/zlib/deflate.h rename to deps/src/zlib/deflate.h index cbf0d1ea5..ce0299edd 100644 --- a/third_party/zlib/deflate.h +++ b/deps/src/zlib/deflate.h @@ -1,5 +1,5 @@ /* deflate.h -- internal compression state - * Copyright (C) 1995-2010 Jean-loup Gailly + * Copyright (C) 1995-2012 Jean-loup Gailly * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -48,6 +48,9 @@ #define MAX_BITS 15 /* All codes must not exceed MAX_BITS bits */ +#define Buf_size 16 +/* size of bit buffer in bi_buf */ + #define INIT_STATE 42 #define EXTRA_STATE 69 #define NAME_STATE 73 @@ -101,7 +104,7 @@ typedef struct internal_state { int wrap; /* bit 0 true for zlib, bit 1 true for gzip */ gz_headerp gzhead; /* gzip header information to write */ uInt gzindex; /* where in extra, name, or comment */ - Byte method; /* STORED (for zip only) or DEFLATED */ + Byte method; /* can only be DEFLATED */ int last_flush; /* value of flush param for previous deflate call */ /* used by deflate.c: */ @@ -188,7 +191,7 @@ typedef struct internal_state { int nice_match; /* Stop searching when current match exceeds this */ /* used by trees.c: */ - /* Didn't use ct_data typedef below to supress compiler warning */ + /* Didn't use ct_data typedef below to suppress compiler warning */ struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ @@ -244,7 +247,7 @@ typedef struct internal_state { ulg opt_len; /* bit length of current block with optimal trees */ ulg static_len; /* bit length of current block with static trees */ uInt matches; /* number of string matches in current block */ - int last_eob_len; /* bit length of EOB code for last block */ + uInt insert; /* bytes at end of window left to insert */ #ifdef DEBUG ulg compressed_len; /* total bit length of compressed file mod 2^32 */ @@ -294,6 +297,7 @@ void ZLIB_INTERNAL _tr_init OF((deflate_state *s)); int ZLIB_INTERNAL _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc)); void ZLIB_INTERNAL _tr_flush_block OF((deflate_state *s, charf *buf, ulg stored_len, int last)); +void ZLIB_INTERNAL _tr_flush_bits OF((deflate_state *s)); void ZLIB_INTERNAL _tr_align OF((deflate_state *s)); void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf, ulg stored_len, int last)); diff --git a/third_party/zlib/gzclose.c b/deps/src/zlib/gzclose.c similarity index 100% rename from third_party/zlib/gzclose.c rename to deps/src/zlib/gzclose.c diff --git a/third_party/zlib/gzguts.h b/deps/src/zlib/gzguts.h similarity index 63% rename from third_party/zlib/gzguts.h rename to deps/src/zlib/gzguts.h index 0f8fb79f8..d87659d03 100644 --- a/third_party/zlib/gzguts.h +++ b/deps/src/zlib/gzguts.h @@ -1,5 +1,5 @@ /* gzguts.h -- zlib internal header definitions for gz* operations - * Copyright (C) 2004, 2005, 2010 Mark Adler + * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -12,7 +12,7 @@ # endif #endif -#if ((__GNUC__-0) * 10 + __GNUC_MINOR__-0 >= 33) && !defined(NO_VIZ) +#ifdef HAVE_HIDDEN # define ZLIB_INTERNAL __attribute__((visibility ("hidden"))) #else # define ZLIB_INTERNAL @@ -27,13 +27,80 @@ #endif #include +#ifdef _WIN32 +# include +#endif + +#if defined(__TURBOC__) || defined(_MSC_VER) || defined(_WIN32) +# include +#endif + +#ifdef WINAPI_FAMILY +# define open _open +# define read _read +# define write _write +# define close _close +#endif + #ifdef NO_DEFLATE /* for compatibility with old definition */ # define NO_GZCOMPRESS #endif +#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif + +#if defined(__CYGWIN__) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif + +#if defined(MSDOS) && defined(__BORLANDC__) && (BORLANDC > 0x410) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif + +#ifndef HAVE_VSNPRINTF +# ifdef MSDOS +/* vsnprintf may exist on some MS-DOS compilers (DJGPP?), + but for now we just assume it doesn't. */ +# define NO_vsnprintf +# endif +# ifdef __TURBOC__ +# define NO_vsnprintf +# endif +# ifdef WIN32 +/* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */ +# if !defined(vsnprintf) && !defined(NO_vsnprintf) +# if !defined(_MSC_VER) || ( defined(_MSC_VER) && _MSC_VER < 1500 ) +# define vsnprintf _vsnprintf +# endif +# endif +# endif +# ifdef __SASC +# define NO_vsnprintf +# endif +# ifdef VMS +# define NO_vsnprintf +# endif +# ifdef __OS400__ +# define NO_vsnprintf +# endif +# ifdef __MVS__ +# define NO_vsnprintf +# endif +#endif + +/* unlike snprintf (which is required in C99, yet still not supported by + Microsoft more than a decade later!), _snprintf does not guarantee null + termination of the result -- however this is only used in gzlib.c where + the result is assured to fit in the space provided */ #ifdef _MSC_VER -# include -# define vsnprintf _vsnprintf +# define snprintf _snprintf #endif #ifndef local @@ -52,7 +119,7 @@ # include # define zstrerror() gz_strwinerror((DWORD)GetLastError()) #else -# ifdef STDC +# ifndef NO_STRERROR # include # define zstrerror() strerror(errno) # else @@ -68,7 +135,15 @@ ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile)); #endif -/* default i/o buffer size -- double this for output when reading */ +/* default memLevel */ +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif + +/* default i/o buffer size -- double this for output when reading (this and + twice this must be able to fit in an unsigned type) */ #define GZBUFSIZE 8192 /* gzip modes, also provide a little integrity check on the passed structure */ @@ -84,23 +159,25 @@ /* internal gzip file state data structure */ typedef struct { + /* exposed contents for gzgetc() macro */ + struct gzFile_s x; /* "x" for exposed */ + /* x.have: number of bytes available at x.next */ + /* x.next: next output data to deliver or write */ + /* x.pos: current position in uncompressed data */ /* used for both reading and writing */ int mode; /* see gzip modes above */ int fd; /* file descriptor */ char *path; /* path or fd for error messages */ - z_off64_t pos; /* current position in uncompressed data */ unsigned size; /* buffer size, zero if not allocated yet */ unsigned want; /* requested buffer size, default is GZBUFSIZE */ unsigned char *in; /* input buffer */ unsigned char *out; /* output buffer (double-sized when reading) */ - unsigned char *next; /* next output data to deliver or write */ + int direct; /* 0 if processing gzip, 1 if transparent */ /* just for reading */ - unsigned have; /* amount of output data unused at next */ - int eof; /* true if end of input file reached */ - z_off64_t start; /* where the gzip data started, for rewinding */ - z_off64_t raw; /* where the raw data started, for seeking */ int how; /* 0: get header, 1: copy, 2: decompress */ - int direct; /* true if last read direct, false if gzip */ + z_off64_t start; /* where the gzip data started, for rewinding */ + int eof; /* true if end of input file reached */ + int past; /* true if read requested past end */ /* just for writing */ int level; /* compression level */ int strategy; /* compression strategy */ diff --git a/third_party/zlib/gzlib.c b/deps/src/zlib/gzlib.c similarity index 73% rename from third_party/zlib/gzlib.c rename to deps/src/zlib/gzlib.c index 603e60ed5..fae202ef8 100644 --- a/third_party/zlib/gzlib.c +++ b/deps/src/zlib/gzlib.c @@ -1,19 +1,23 @@ /* gzlib.c -- zlib functions common to reading and writing gzip files - * Copyright (C) 2004, 2010 Mark Adler + * Copyright (C) 2004, 2010, 2011, 2012, 2013 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ #include "gzguts.h" +#if defined(_WIN32) && !defined(__BORLANDC__) +# define LSEEK _lseeki64 +#else #if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0 # define LSEEK lseek64 #else # define LSEEK lseek #endif +#endif /* Local functions */ local void gz_reset OF((gz_statep)); -local gzFile gz_open OF((const char *, int, const char *)); +local gzFile gz_open OF((const void *, int, const char *)); #if defined UNDER_CE @@ -71,28 +75,40 @@ char ZLIB_INTERNAL *gz_strwinerror (error) local void gz_reset(state) gz_statep state; { + state->x.have = 0; /* no output data available */ if (state->mode == GZ_READ) { /* for reading ... */ - state->have = 0; /* no output data available */ state->eof = 0; /* not at end of file */ + state->past = 0; /* have not read past end yet */ state->how = LOOK; /* look for gzip header */ - state->direct = 1; /* default for empty file */ } state->seek = 0; /* no seek request pending */ gz_error(state, Z_OK, NULL); /* clear error */ - state->pos = 0; /* no uncompressed data yet */ + state->x.pos = 0; /* no uncompressed data yet */ state->strm.avail_in = 0; /* no input data yet */ } /* Open a gzip file either by name or file descriptor. */ local gzFile gz_open(path, fd, mode) - const char *path; + const void *path; int fd; const char *mode; { gz_statep state; + size_t len; + int oflag; +#ifdef O_CLOEXEC + int cloexec = 0; +#endif +#ifdef O_EXCL + int exclusive = 0; +#endif + + /* check input */ + if (path == NULL) + return NULL; /* allocate gzFile structure to return */ - state = malloc(sizeof(gz_state)); + state = (gz_statep)malloc(sizeof(gz_state)); if (state == NULL) return NULL; state->size = 0; /* no buffers allocated yet */ @@ -103,6 +119,7 @@ local gzFile gz_open(path, fd, mode) state->mode = GZ_NONE; state->level = Z_DEFAULT_COMPRESSION; state->strategy = Z_DEFAULT_STRATEGY; + state->direct = 0; while (*mode) { if (*mode >= '0' && *mode <= '9') state->level = *mode - '0'; @@ -124,6 +141,16 @@ local gzFile gz_open(path, fd, mode) return NULL; case 'b': /* ignore -- will request binary anyway */ break; +#ifdef O_CLOEXEC + case 'e': + cloexec = 1; + break; +#endif +#ifdef O_EXCL + case 'x': + exclusive = 1; + break; +#endif case 'f': state->strategy = Z_FILTERED; break; @@ -135,6 +162,10 @@ local gzFile gz_open(path, fd, mode) break; case 'F': state->strategy = Z_FIXED; + break; + case 'T': + state->direct = 1; + break; default: /* could consider as an error, but just ignore */ ; } @@ -147,30 +178,71 @@ local gzFile gz_open(path, fd, mode) return NULL; } + /* can't force transparent read */ + if (state->mode == GZ_READ) { + if (state->direct) { + free(state); + return NULL; + } + state->direct = 1; /* for empty file */ + } + /* save the path name for error messages */ - state->path = malloc(strlen(path) + 1); +#ifdef _WIN32 + if (fd == -2) { + len = wcstombs(NULL, path, 0); + if (len == (size_t)-1) + len = 0; + } + else +#endif + len = strlen((const char *)path); + state->path = (char *)malloc(len + 1); if (state->path == NULL) { free(state); return NULL; } - strcpy(state->path, path); +#ifdef _WIN32 + if (fd == -2) + if (len) + wcstombs(state->path, path, len + 1); + else + *(state->path) = 0; + else +#endif +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(state->path, len + 1, "%s", (const char *)path); +#else + strcpy(state->path, path); +#endif - /* open the file with the appropriate mode (or just use fd) */ - state->fd = fd != -1 ? fd : - open(path, + /* compute the flags for open() */ + oflag = #ifdef O_LARGEFILE - O_LARGEFILE | + O_LARGEFILE | #endif #ifdef O_BINARY - O_BINARY | + O_BINARY | +#endif +#ifdef O_CLOEXEC + (cloexec ? O_CLOEXEC : 0) | +#endif + (state->mode == GZ_READ ? + O_RDONLY : + (O_WRONLY | O_CREAT | +#ifdef O_EXCL + (exclusive ? O_EXCL : 0) | +#endif + (state->mode == GZ_WRITE ? + O_TRUNC : + O_APPEND))); + + /* open the file with the appropriate flags (or just use fd) */ + state->fd = fd > -1 ? fd : ( +#ifdef _WIN32 + fd == -2 ? _wopen(path, oflag, 0666) : #endif - (state->mode == GZ_READ ? - O_RDONLY : - (O_WRONLY | O_CREAT | ( - state->mode == GZ_WRITE ? - O_TRUNC : - O_APPEND))), - 0666); + open((const char *)path, oflag, 0666)); if (state->fd == -1) { free(state->path); free(state); @@ -216,14 +288,28 @@ gzFile ZEXPORT gzdopen(fd, mode) char *path; /* identifier for error messages */ gzFile gz; - if (fd == -1 || (path = malloc(7 + 3 * sizeof(int))) == NULL) + if (fd == -1 || (path = (char *)malloc(7 + 3 * sizeof(int))) == NULL) return NULL; +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(path, 7 + 3 * sizeof(int), "", fd); /* for debugging */ +#else sprintf(path, "", fd); /* for debugging */ +#endif gz = gz_open(path, fd, mode); free(path); return gz; } +/* -- see zlib.h -- */ +#ifdef _WIN32 +gzFile ZEXPORT gzopen_w(path, mode) + const wchar_t *path; + const char *mode; +{ + return gz_open(path, -2, mode); +} +#endif + /* -- see zlib.h -- */ int ZEXPORT gzbuffer(file, size) gzFile file; @@ -243,8 +329,8 @@ int ZEXPORT gzbuffer(file, size) return -1; /* check and set requested size */ - if (size == 0) - return -1; + if (size < 2) + size = 2; /* need two bytes to check magic header */ state->want = size; return 0; } @@ -261,7 +347,8 @@ int ZEXPORT gzrewind(file) state = (gz_statep)file; /* check that we're reading and that there's no error */ - if (state->mode != GZ_READ || state->err != Z_OK) + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) return -1; /* back up and start over */ @@ -289,7 +376,7 @@ z_off64_t ZEXPORT gzseek64(file, offset, whence) return -1; /* check that there's no error */ - if (state->err != Z_OK) + if (state->err != Z_OK && state->err != Z_BUF_ERROR) return -1; /* can only seek from start or relative to current position */ @@ -298,31 +385,32 @@ z_off64_t ZEXPORT gzseek64(file, offset, whence) /* normalize offset to a SEEK_CUR specification */ if (whence == SEEK_SET) - offset -= state->pos; + offset -= state->x.pos; else if (state->seek) offset += state->skip; state->seek = 0; /* if within raw area while reading, just go there */ if (state->mode == GZ_READ && state->how == COPY && - state->pos + offset >= state->raw) { - ret = LSEEK(state->fd, offset - state->have, SEEK_CUR); + state->x.pos + offset >= 0) { + ret = LSEEK(state->fd, offset - state->x.have, SEEK_CUR); if (ret == -1) return -1; - state->have = 0; + state->x.have = 0; state->eof = 0; + state->past = 0; state->seek = 0; gz_error(state, Z_OK, NULL); state->strm.avail_in = 0; - state->pos += offset; - return state->pos; + state->x.pos += offset; + return state->x.pos; } /* calculate skip amount, rewinding if needed for back seek when reading */ if (offset < 0) { if (state->mode != GZ_READ) /* writing -- can't go backwards */ return -1; - offset += state->pos; + offset += state->x.pos; if (offset < 0) /* before start of file! */ return -1; if (gzrewind(file) == -1) /* rewind, then skip to offset */ @@ -331,11 +419,11 @@ z_off64_t ZEXPORT gzseek64(file, offset, whence) /* if reading, skip what's in output buffer (one less gzgetc() check) */ if (state->mode == GZ_READ) { - n = GT_OFF(state->have) || (z_off64_t)state->have > offset ? - (unsigned)offset : state->have; - state->have -= n; - state->next += n; - state->pos += n; + n = GT_OFF(state->x.have) || (z_off64_t)state->x.have > offset ? + (unsigned)offset : state->x.have; + state->x.have -= n; + state->x.next += n; + state->x.pos += n; offset -= n; } @@ -344,7 +432,7 @@ z_off64_t ZEXPORT gzseek64(file, offset, whence) state->seek = 1; state->skip = offset; } - return state->pos + offset; + return state->x.pos + offset; } /* -- see zlib.h -- */ @@ -373,7 +461,7 @@ z_off64_t ZEXPORT gztell64(file) return -1; /* return position */ - return state->pos + (state->seek ? state->skip : 0); + return state->x.pos + (state->seek ? state->skip : 0); } /* -- see zlib.h -- */ @@ -433,8 +521,7 @@ int ZEXPORT gzeof(file) return 0; /* return end-of-file state */ - return state->mode == GZ_READ ? - (state->eof && state->strm.avail_in == 0 && state->have == 0) : 0; + return state->mode == GZ_READ ? state->past : 0; } /* -- see zlib.h -- */ @@ -454,7 +541,8 @@ const char * ZEXPORT gzerror(file, errnum) /* return error information */ if (errnum != NULL) *errnum = state->err; - return state->msg == NULL ? "" : state->msg; + return state->err == Z_MEM_ERROR ? "out of memory" : + (state->msg == NULL ? "" : state->msg); } /* -- see zlib.h -- */ @@ -471,8 +559,10 @@ void ZEXPORT gzclearerr(file) return; /* clear error and end-of-file */ - if (state->mode == GZ_READ) + if (state->mode == GZ_READ) { state->eof = 0; + state->past = 0; + } gz_error(state, Z_OK, NULL); } @@ -494,26 +584,33 @@ void ZLIB_INTERNAL gz_error(state, err, msg) state->msg = NULL; } + /* if fatal, set state->x.have to 0 so that the gzgetc() macro fails */ + if (err != Z_OK && err != Z_BUF_ERROR) + state->x.have = 0; + /* set error code, and if no message, then done */ state->err = err; if (msg == NULL) return; - /* for an out of memory error, save as static string */ - if (err == Z_MEM_ERROR) { - state->msg = (char *)msg; + /* for an out of memory error, return literal string when requested */ + if (err == Z_MEM_ERROR) return; - } /* construct error message with path */ - if ((state->msg = malloc(strlen(state->path) + strlen(msg) + 3)) == NULL) { + if ((state->msg = (char *)malloc(strlen(state->path) + strlen(msg) + 3)) == + NULL) { state->err = Z_MEM_ERROR; - state->msg = (char *)"out of memory"; return; } +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(state->msg, strlen(state->path) + strlen(msg) + 3, + "%s%s%s", state->path, ": ", msg); +#else strcpy(state->msg, state->path); strcat(state->msg, ": "); strcat(state->msg, msg); +#endif return; } diff --git a/third_party/zlib/gzread.c b/deps/src/zlib/gzread.c similarity index 51% rename from third_party/zlib/gzread.c rename to deps/src/zlib/gzread.c index 548201ab0..bf4538eb2 100644 --- a/third_party/zlib/gzread.c +++ b/deps/src/zlib/gzread.c @@ -1,5 +1,5 @@ /* gzread.c -- zlib functions for reading gzip files - * Copyright (C) 2004, 2005, 2010 Mark Adler + * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -8,10 +8,9 @@ /* Local functions */ local int gz_load OF((gz_statep, unsigned char *, unsigned, unsigned *)); local int gz_avail OF((gz_statep)); -local int gz_next4 OF((gz_statep, unsigned long *)); -local int gz_head OF((gz_statep)); +local int gz_look OF((gz_statep)); local int gz_decomp OF((gz_statep)); -local int gz_make OF((gz_statep)); +local int gz_fetch OF((gz_statep)); local int gz_skip OF((gz_statep, z_off64_t)); /* Use read() to load a buffer -- return -1 on error, otherwise 0. Read from @@ -46,73 +45,54 @@ local int gz_load(state, buf, len, have) error, 0 otherwise. Note that the eof flag is set when the end of the input file is reached, even though there may be unused data in the buffer. Once that data has been used, no more attempts will be made to read the file. - gz_avail() assumes that strm->avail_in == 0. */ + If strm->avail_in != 0, then the current data is moved to the beginning of + the input buffer, and then the remainder of the buffer is loaded with the + available data from the input file. */ local int gz_avail(state) gz_statep state; { + unsigned got; z_streamp strm = &(state->strm); - if (state->err != Z_OK) + if (state->err != Z_OK && state->err != Z_BUF_ERROR) return -1; if (state->eof == 0) { - if (gz_load(state, state->in, state->size, - (unsigned *)&(strm->avail_in)) == -1) + if (strm->avail_in) { /* copy what's there to the start */ + unsigned char *p = state->in; + unsigned const char *q = strm->next_in; + unsigned n = strm->avail_in; + do { + *p++ = *q++; + } while (--n); + } + if (gz_load(state, state->in + strm->avail_in, + state->size - strm->avail_in, &got) == -1) return -1; + strm->avail_in += got; strm->next_in = state->in; } return 0; } -/* Get next byte from input, or -1 if end or error. */ -#define NEXT() ((strm->avail_in == 0 && gz_avail(state) == -1) ? -1 : \ - (strm->avail_in == 0 ? -1 : \ - (strm->avail_in--, *(strm->next_in)++))) - -/* Get a four-byte little-endian integer and return 0 on success and the value - in *ret. Otherwise -1 is returned and *ret is not modified. */ -local int gz_next4(state, ret) - gz_statep state; - unsigned long *ret; -{ - int ch; - unsigned long val; - z_streamp strm = &(state->strm); - - val = NEXT(); - val += (unsigned)NEXT() << 8; - val += (unsigned long)NEXT() << 16; - ch = NEXT(); - if (ch == -1) - return -1; - val += (unsigned long)ch << 24; - *ret = val; - return 0; -} - -/* Look for gzip header, set up for inflate or copy. state->have must be zero. +/* Look for gzip header, set up for inflate or copy. state->x.have must be 0. If this is the first time in, allocate required memory. state->how will be left unchanged if there is no more input data available, will be set to COPY if there is no gzip header and direct copying will be performed, or it will - be set to GZIP for decompression, and the gzip header will be skipped so - that the next available input data is the raw deflate stream. If direct - copying, then leftover input data from the input buffer will be copied to - the output buffer. In that case, all further file reads will be directly to - either the output buffer or a user buffer. If decompressing, the inflate - state and the check value will be initialized. gz_head() will return 0 on - success or -1 on failure. Failures may include read errors or gzip header - errors. */ -local int gz_head(state) + be set to GZIP for decompression. If direct copying, then leftover input + data from the input buffer will be copied to the output buffer. In that + case, all further file reads will be directly to either the output buffer or + a user buffer. If decompressing, the inflate state will be initialized. + gz_look() will return 0 on success or -1 on failure. */ +local int gz_look(state) gz_statep state; { z_streamp strm = &(state->strm); - int flags; - unsigned len; /* allocate read buffers and inflate memory */ if (state->size == 0) { /* allocate buffers */ - state->in = malloc(state->want); - state->out = malloc(state->want << 1); + state->in = (unsigned char *)malloc(state->want); + state->out = (unsigned char *)malloc(state->want << 1); if (state->in == NULL || state->out == NULL) { if (state->out != NULL) free(state->out); @@ -129,7 +109,7 @@ local int gz_head(state) state->strm.opaque = Z_NULL; state->strm.avail_in = 0; state->strm.next_in = Z_NULL; - if (inflateInit2(&(state->strm), -15) != Z_OK) { /* raw inflate */ + if (inflateInit2(&(state->strm), 15 + 16) != Z_OK) { /* gunzip */ free(state->out); free(state->in); state->size = 0; @@ -138,83 +118,45 @@ local int gz_head(state) } } - /* get some data in the input buffer */ - if (strm->avail_in == 0) { + /* get at least the magic bytes in the input buffer */ + if (strm->avail_in < 2) { if (gz_avail(state) == -1) return -1; if (strm->avail_in == 0) return 0; } - /* look for the gzip magic header bytes 31 and 139 */ - if (strm->next_in[0] == 31) { - strm->avail_in--; - strm->next_in++; - if (strm->avail_in == 0 && gz_avail(state) == -1) - return -1; - if (strm->avail_in && strm->next_in[0] == 139) { - /* we have a gzip header, woo hoo! */ - strm->avail_in--; - strm->next_in++; - - /* skip rest of header */ - if (NEXT() != 8) { /* compression method */ - gz_error(state, Z_DATA_ERROR, "unknown compression method"); - return -1; - } - flags = NEXT(); - if (flags & 0xe0) { /* reserved flag bits */ - gz_error(state, Z_DATA_ERROR, "unknown header flags set"); - return -1; - } - NEXT(); /* modification time */ - NEXT(); - NEXT(); - NEXT(); - NEXT(); /* extra flags */ - NEXT(); /* operating system */ - if (flags & 4) { /* extra field */ - len = (unsigned)NEXT(); - len += (unsigned)NEXT() << 8; - while (len--) - if (NEXT() < 0) - break; - } - if (flags & 8) /* file name */ - while (NEXT() > 0) - ; - if (flags & 16) /* comment */ - while (NEXT() > 0) - ; - if (flags & 2) { /* header crc */ - NEXT(); - NEXT(); - } - /* an unexpected end of file is not checked for here -- it will be - noticed on the first request for uncompressed data */ - - /* set up for decompression */ - inflateReset(strm); - strm->adler = crc32(0L, Z_NULL, 0); - state->how = GZIP; - state->direct = 0; - return 0; - } - else { - /* not a gzip file -- save first byte (31) and fall to raw i/o */ - state->out[0] = 31; - state->have = 1; - } + /* look for gzip magic bytes -- if there, do gzip decoding (note: there is + a logical dilemma here when considering the case of a partially written + gzip file, to wit, if a single 31 byte is written, then we cannot tell + whether this is a single-byte file, or just a partially written gzip + file -- for here we assume that if a gzip file is being written, then + the header will be written in a single operation, so that reading a + single byte is sufficient indication that it is not a gzip file) */ + if (strm->avail_in > 1 && + strm->next_in[0] == 31 && strm->next_in[1] == 139) { + inflateReset(strm); + state->how = GZIP; + state->direct = 0; + return 0; + } + + /* no gzip header -- if we were decoding gzip before, then this is trailing + garbage. Ignore the trailing garbage and finish. */ + if (state->direct == 0) { + strm->avail_in = 0; + state->eof = 1; + state->x.have = 0; + return 0; } - /* doing raw i/o, save start of raw data for seeking, copy any leftover - input to output -- this assumes that the output buffer is larger than - the input buffer, which also assures space for gzungetc() */ - state->raw = state->pos; - state->next = state->out; + /* doing raw i/o, copy any leftover input to output -- this assumes that + the output buffer is larger than the input buffer, which also assures + space for gzungetc() */ + state->x.next = state->out; if (strm->avail_in) { - memcpy(state->next + state->have, strm->next_in, strm->avail_in); - state->have += strm->avail_in; + memcpy(state->x.next, strm->next_in, strm->avail_in); + state->x.have = strm->avail_in; strm->avail_in = 0; } state->how = COPY; @@ -223,19 +165,15 @@ local int gz_head(state) } /* Decompress from input to the provided next_out and avail_out in the state. - If the end of the compressed data is reached, then verify the gzip trailer - check value and length (modulo 2^32). state->have and state->next are set - to point to the just decompressed data, and the crc is updated. If the - trailer is verified, state->how is reset to LOOK to look for the next gzip - stream or raw data, once state->have is depleted. Returns 0 on success, -1 - on failure. Failures may include invalid compressed data or a failed gzip - trailer verification. */ + On return, state->x.have and state->x.next point to the just decompressed + data. If the gzip stream completes, state->how is reset to LOOK to look for + the next gzip stream or raw data, once state->x.have is depleted. Returns 0 + on success, -1 on failure. */ local int gz_decomp(state) gz_statep state; { - int ret; + int ret = Z_OK; unsigned had; - unsigned long crc, len; z_streamp strm = &(state->strm); /* fill output buffer up to end of deflate stream */ @@ -245,15 +183,15 @@ local int gz_decomp(state) if (strm->avail_in == 0 && gz_avail(state) == -1) return -1; if (strm->avail_in == 0) { - gz_error(state, Z_DATA_ERROR, "unexpected end of file"); - return -1; + gz_error(state, Z_BUF_ERROR, "unexpected end of file"); + break; } /* decompress and handle errors */ ret = inflate(strm, Z_NO_FLUSH); if (ret == Z_STREAM_ERROR || ret == Z_NEED_DICT) { gz_error(state, Z_STREAM_ERROR, - "internal error: inflate stream corrupt"); + "internal error: inflate stream corrupt"); return -1; } if (ret == Z_MEM_ERROR) { @@ -262,67 +200,55 @@ local int gz_decomp(state) } if (ret == Z_DATA_ERROR) { /* deflate stream invalid */ gz_error(state, Z_DATA_ERROR, - strm->msg == NULL ? "compressed data error" : strm->msg); + strm->msg == NULL ? "compressed data error" : strm->msg); return -1; } } while (strm->avail_out && ret != Z_STREAM_END); - /* update available output and crc check value */ - state->have = had - strm->avail_out; - state->next = strm->next_out - state->have; - strm->adler = crc32(strm->adler, state->next, state->have); + /* update available output */ + state->x.have = had - strm->avail_out; + state->x.next = strm->next_out - state->x.have; - /* check gzip trailer if at end of deflate stream */ - if (ret == Z_STREAM_END) { - if (gz_next4(state, &crc) == -1 || gz_next4(state, &len) == -1) { - gz_error(state, Z_DATA_ERROR, "unexpected end of file"); - return -1; - } - if (crc != strm->adler) { - gz_error(state, Z_DATA_ERROR, "incorrect data check"); - return -1; - } - if (len != (strm->total_out & 0xffffffffL)) { - gz_error(state, Z_DATA_ERROR, "incorrect length check"); - return -1; - } - state->how = LOOK; /* ready for next stream, once have is 0 (leave - state->direct unchanged to remember how) */ - } + /* if the gzip stream completed successfully, look for another */ + if (ret == Z_STREAM_END) + state->how = LOOK; /* good decompression */ return 0; } -/* Make data and put in the output buffer. Assumes that state->have == 0. +/* Fetch data and put it in the output buffer. Assumes state->x.have is 0. Data is either copied from the input file or decompressed from the input file depending on state->how. If state->how is LOOK, then a gzip header is - looked for (and skipped if found) to determine wither to copy or decompress. - Returns -1 on error, otherwise 0. gz_make() will leave state->have as COPY - or GZIP unless the end of the input file has been reached and all data has - been processed. */ -local int gz_make(state) + looked for to determine whether to copy or decompress. Returns -1 on error, + otherwise 0. gz_fetch() will leave state->how as COPY or GZIP unless the + end of the input file has been reached and all data has been processed. */ +local int gz_fetch(state) gz_statep state; { z_streamp strm = &(state->strm); - if (state->how == LOOK) { /* look for gzip header */ - if (gz_head(state) == -1) - return -1; - if (state->have) /* got some data from gz_head() */ + do { + switch(state->how) { + case LOOK: /* -> LOOK, COPY (only if never GZIP), or GZIP */ + if (gz_look(state) == -1) + return -1; + if (state->how == LOOK) + return 0; + break; + case COPY: /* -> COPY */ + if (gz_load(state, state->out, state->size << 1, &(state->x.have)) + == -1) + return -1; + state->x.next = state->out; return 0; - } - if (state->how == COPY) { /* straight copy */ - if (gz_load(state, state->out, state->size << 1, &(state->have)) == -1) - return -1; - state->next = state->out; - } - else if (state->how == GZIP) { /* decompress */ - strm->avail_out = state->size << 1; - strm->next_out = state->out; - if (gz_decomp(state) == -1) - return -1; - } + case GZIP: /* -> GZIP or LOOK (if end of gzip stream) */ + strm->avail_out = state->size << 1; + strm->next_out = state->out; + if (gz_decomp(state) == -1) + return -1; + } + } while (state->x.have == 0 && (!state->eof || strm->avail_in)); return 0; } @@ -336,12 +262,12 @@ local int gz_skip(state, len) /* skip over len bytes or reach end-of-file, whichever comes first */ while (len) /* skip over whatever is in output buffer */ - if (state->have) { - n = GT_OFF(state->have) || (z_off64_t)state->have > len ? - (unsigned)len : state->have; - state->have -= n; - state->next += n; - state->pos += n; + if (state->x.have) { + n = GT_OFF(state->x.have) || (z_off64_t)state->x.have > len ? + (unsigned)len : state->x.have; + state->x.have -= n; + state->x.next += n; + state->x.pos += n; len -= n; } @@ -352,7 +278,7 @@ local int gz_skip(state, len) /* need more data to skip -- load up output buffer */ else { /* get more output, looking for header if required */ - if (gz_make(state) == -1) + if (gz_fetch(state) == -1) return -1; } return 0; @@ -374,14 +300,15 @@ int ZEXPORT gzread(file, buf, len) state = (gz_statep)file; strm = &(state->strm); - /* check that we're reading and that there's no error */ - if (state->mode != GZ_READ || state->err != Z_OK) + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) return -1; /* since an int is returned, make sure len fits in one, otherwise return with an error (this avoids the flaw in the interface) */ if ((int)len < 0) { - gz_error(state, Z_BUF_ERROR, "requested length does not fit in int"); + gz_error(state, Z_DATA_ERROR, "requested length does not fit in int"); return -1; } @@ -400,49 +327,51 @@ int ZEXPORT gzread(file, buf, len) got = 0; do { /* first just try copying data from the output buffer */ - if (state->have) { - n = state->have > len ? len : state->have; - memcpy(buf, state->next, n); - state->next += n; - state->have -= n; + if (state->x.have) { + n = state->x.have > len ? len : state->x.have; + memcpy(buf, state->x.next, n); + state->x.next += n; + state->x.have -= n; } /* output buffer empty -- return if we're at the end of the input */ - else if (state->eof && strm->avail_in == 0) + else if (state->eof && strm->avail_in == 0) { + state->past = 1; /* tried to read past end */ break; + } /* need output data -- for small len or new stream load up our output buffer */ else if (state->how == LOOK || len < (state->size << 1)) { /* get more output, looking for header if required */ - if (gz_make(state) == -1) + if (gz_fetch(state) == -1) return -1; - continue; /* no progress yet -- go back to memcpy() above */ + continue; /* no progress yet -- go back to copy above */ /* the copy above assures that we will leave with space in the output buffer, allowing at least one gzungetc() to succeed */ } /* large len -- read directly into user buffer */ else if (state->how == COPY) { /* read directly */ - if (gz_load(state, buf, len, &n) == -1) + if (gz_load(state, (unsigned char *)buf, len, &n) == -1) return -1; } /* large len -- decompress directly into user buffer */ else { /* state->how == GZIP */ strm->avail_out = len; - strm->next_out = buf; + strm->next_out = (unsigned char *)buf; if (gz_decomp(state) == -1) return -1; - n = state->have; - state->have = 0; + n = state->x.have; + state->x.have = 0; } /* update progress */ len -= n; buf = (char *)buf + n; got += n; - state->pos += n; + state->x.pos += n; } while (len); /* return number of bytes read into user buffer (will fit in int) */ @@ -450,6 +379,11 @@ int ZEXPORT gzread(file, buf, len) } /* -- see zlib.h -- */ +#ifdef Z_PREFIX_SET +# undef z_gzgetc +#else +# undef gzgetc +#endif int ZEXPORT gzgetc(file) gzFile file; { @@ -462,15 +396,16 @@ int ZEXPORT gzgetc(file) return -1; state = (gz_statep)file; - /* check that we're reading and that there's no error */ - if (state->mode != GZ_READ || state->err != Z_OK) + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) return -1; /* try output buffer (no need to check for skip request) */ - if (state->have) { - state->have--; - state->pos++; - return *(state->next)++; + if (state->x.have) { + state->x.have--; + state->x.pos++; + return *(state->x.next)++; } /* nothing there -- try gzread() */ @@ -478,6 +413,12 @@ int ZEXPORT gzgetc(file) return ret < 1 ? -1 : buf[0]; } +int ZEXPORT gzgetc_(file) +gzFile file; +{ + return gzgetc(file); +} + /* -- see zlib.h -- */ int ZEXPORT gzungetc(c, file) int c; @@ -490,8 +431,9 @@ int ZEXPORT gzungetc(c, file) return -1; state = (gz_statep)file; - /* check that we're reading and that there's no error */ - if (state->mode != GZ_READ || state->err != Z_OK) + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) return -1; /* process a skip request */ @@ -506,32 +448,34 @@ int ZEXPORT gzungetc(c, file) return -1; /* if output buffer empty, put byte at end (allows more pushing) */ - if (state->have == 0) { - state->have = 1; - state->next = state->out + (state->size << 1) - 1; - state->next[0] = c; - state->pos--; + if (state->x.have == 0) { + state->x.have = 1; + state->x.next = state->out + (state->size << 1) - 1; + state->x.next[0] = c; + state->x.pos--; + state->past = 0; return c; } /* if no room, give up (must have already done a gzungetc()) */ - if (state->have == (state->size << 1)) { - gz_error(state, Z_BUF_ERROR, "out of room to push characters"); + if (state->x.have == (state->size << 1)) { + gz_error(state, Z_DATA_ERROR, "out of room to push characters"); return -1; } /* slide output data if needed and insert byte before existing data */ - if (state->next == state->out) { - unsigned char *src = state->out + state->have; + if (state->x.next == state->out) { + unsigned char *src = state->out + state->x.have; unsigned char *dest = state->out + (state->size << 1); while (src > state->out) *--dest = *--src; - state->next = dest; + state->x.next = dest; } - state->have++; - state->next--; - state->next[0] = c; - state->pos--; + state->x.have++; + state->x.next--; + state->x.next[0] = c; + state->x.pos--; + state->past = 0; return c; } @@ -551,8 +495,9 @@ char * ZEXPORT gzgets(file, buf, len) return NULL; state = (gz_statep)file; - /* check that we're reading and that there's no error */ - if (state->mode != GZ_READ || state->err != Z_OK) + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) return NULL; /* process a skip request */ @@ -569,32 +514,31 @@ char * ZEXPORT gzgets(file, buf, len) left = (unsigned)len - 1; if (left) do { /* assure that something is in the output buffer */ - if (state->have == 0) { - if (gz_make(state) == -1) - return NULL; /* error */ - if (state->have == 0) { /* end of file */ - if (buf == str) /* got bupkus */ - return NULL; - break; /* got something -- return it */ - } + if (state->x.have == 0 && gz_fetch(state) == -1) + return NULL; /* error */ + if (state->x.have == 0) { /* end of file */ + state->past = 1; /* read past end */ + break; /* return what we have */ } /* look for end-of-line in current output buffer */ - n = state->have > left ? left : state->have; - eol = memchr(state->next, '\n', n); + n = state->x.have > left ? left : state->x.have; + eol = (unsigned char *)memchr(state->x.next, '\n', n); if (eol != NULL) - n = (unsigned)(eol - state->next) + 1; + n = (unsigned)(eol - state->x.next) + 1; /* copy through end-of-line, or remainder if not found */ - memcpy(buf, state->next, n); - state->have -= n; - state->next += n; - state->pos += n; + memcpy(buf, state->x.next, n); + state->x.have -= n; + state->x.next += n; + state->x.pos += n; left -= n; buf += n; } while (left && eol == NULL); - /* found end-of-line or out of space -- terminate string and return it */ + /* return terminated string, or if nothing, end of file */ + if (buf == str) + return NULL; buf[0] = 0; return str; } @@ -610,16 +554,12 @@ int ZEXPORT gzdirect(file) return 0; state = (gz_statep)file; - /* check that we're reading */ - if (state->mode != GZ_READ) - return 0; - /* if the state is not known, but we can find out, then do so (this is mainly for right after a gzopen() or gzdopen()) */ - if (state->how == LOOK && state->have == 0) - (void)gz_head(state); + if (state->mode == GZ_READ && state->how == LOOK && state->x.have == 0) + (void)gz_look(state); - /* return 1 if reading direct, 0 if decompressing a gzip stream */ + /* return 1 if transparent, 0 if processing a gzip stream */ return state->direct; } @@ -627,7 +567,7 @@ int ZEXPORT gzdirect(file) int ZEXPORT gzclose_r(file) gzFile file; { - int ret; + int ret, err; gz_statep state; /* get internal structure */ @@ -645,9 +585,10 @@ int ZEXPORT gzclose_r(file) free(state->out); free(state->in); } + err = state->err == Z_BUF_ERROR ? Z_BUF_ERROR : Z_OK; gz_error(state, Z_OK, NULL); free(state->path); ret = close(state->fd); free(state); - return ret ? Z_ERRNO : Z_OK; + return ret ? Z_ERRNO : err; } diff --git a/third_party/zlib/gzwrite.c b/deps/src/zlib/gzwrite.c similarity index 74% rename from third_party/zlib/gzwrite.c rename to deps/src/zlib/gzwrite.c index e8defc688..aa767fbf6 100644 --- a/third_party/zlib/gzwrite.c +++ b/deps/src/zlib/gzwrite.c @@ -1,5 +1,5 @@ /* gzwrite.c -- zlib functions for writing gzip files - * Copyright (C) 2004, 2005, 2010 Mark Adler + * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -18,44 +18,55 @@ local int gz_init(state) int ret; z_streamp strm = &(state->strm); - /* allocate input and output buffers */ - state->in = malloc(state->want); - state->out = malloc(state->want); - if (state->in == NULL || state->out == NULL) { - if (state->out != NULL) - free(state->out); - if (state->in != NULL) - free(state->in); + /* allocate input buffer */ + state->in = (unsigned char *)malloc(state->want); + if (state->in == NULL) { gz_error(state, Z_MEM_ERROR, "out of memory"); return -1; } - /* allocate deflate memory, set up for gzip compression */ - strm->zalloc = Z_NULL; - strm->zfree = Z_NULL; - strm->opaque = Z_NULL; - ret = deflateInit2(strm, state->level, Z_DEFLATED, - 15 + 16, 8, state->strategy); - if (ret != Z_OK) { - free(state->in); - gz_error(state, Z_MEM_ERROR, "out of memory"); - return -1; + /* only need output buffer and deflate state if compressing */ + if (!state->direct) { + /* allocate output buffer */ + state->out = (unsigned char *)malloc(state->want); + if (state->out == NULL) { + free(state->in); + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } + + /* allocate deflate memory, set up for gzip compression */ + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + ret = deflateInit2(strm, state->level, Z_DEFLATED, + MAX_WBITS + 16, DEF_MEM_LEVEL, state->strategy); + if (ret != Z_OK) { + free(state->out); + free(state->in); + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } } /* mark state as initialized */ state->size = state->want; - /* initialize write buffer */ - strm->avail_out = state->size; - strm->next_out = state->out; - state->next = strm->next_out; + /* initialize write buffer if compressing */ + if (!state->direct) { + strm->avail_out = state->size; + strm->next_out = state->out; + state->x.next = strm->next_out; + } return 0; } /* Compress whatever is at avail_in and next_in and write to the output file. Return -1 if there is an error writing to the output file, otherwise 0. flush is assumed to be a valid deflate() flush value. If flush is Z_FINISH, - then the deflate() state is reset to start a new gzip stream. */ + then the deflate() state is reset to start a new gzip stream. If gz->direct + is true, then simply write to the output file without compressing, and + ignore flush. */ local int gz_comp(state, flush) gz_statep state; int flush; @@ -68,6 +79,17 @@ local int gz_comp(state, flush) if (state->size == 0 && gz_init(state) == -1) return -1; + /* write directly if requested */ + if (state->direct) { + got = write(state->fd, strm->next_in, strm->avail_in); + if (got < 0 || (unsigned)got != strm->avail_in) { + gz_error(state, Z_ERRNO, zstrerror()); + return -1; + } + strm->avail_in = 0; + return 0; + } + /* run deflate() on provided input until it produces no more output */ ret = Z_OK; do { @@ -75,8 +97,8 @@ local int gz_comp(state, flush) doing Z_FINISH then don't write until we get to Z_STREAM_END */ if (strm->avail_out == 0 || (flush != Z_NO_FLUSH && (flush != Z_FINISH || ret == Z_STREAM_END))) { - have = (unsigned)(strm->next_out - state->next); - if (have && ((got = write(state->fd, state->next, have)) < 0 || + have = (unsigned)(strm->next_out - state->x.next); + if (have && ((got = write(state->fd, state->x.next, have)) < 0 || (unsigned)got != have)) { gz_error(state, Z_ERRNO, zstrerror()); return -1; @@ -85,7 +107,7 @@ local int gz_comp(state, flush) strm->avail_out = state->size; strm->next_out = state->out; } - state->next = strm->next_out; + state->x.next = strm->next_out; } /* compress */ @@ -131,7 +153,7 @@ local int gz_zero(state, len) } strm->avail_in = n; strm->next_in = state->in; - state->pos += n; + state->x.pos += n; if (gz_comp(state, Z_NO_FLUSH) == -1) return -1; len -= n; @@ -146,7 +168,6 @@ int ZEXPORT gzwrite(file, buf, len) unsigned len; { unsigned put = len; - unsigned n; gz_statep state; z_streamp strm; @@ -163,7 +184,7 @@ int ZEXPORT gzwrite(file, buf, len) /* since an int is returned, make sure len fits in one, otherwise return with an error (this avoids the flaw in the interface) */ if ((int)len < 0) { - gz_error(state, Z_BUF_ERROR, "requested length does not fit in int"); + gz_error(state, Z_DATA_ERROR, "requested length does not fit in int"); return 0; } @@ -186,16 +207,19 @@ int ZEXPORT gzwrite(file, buf, len) if (len < state->size) { /* copy to input buffer, compress when full */ do { + unsigned have, copy; + if (strm->avail_in == 0) strm->next_in = state->in; - n = state->size - strm->avail_in; - if (n > len) - n = len; - memcpy(strm->next_in + strm->avail_in, buf, n); - strm->avail_in += n; - state->pos += n; - buf = (char *)buf + n; - len -= n; + have = (unsigned)((strm->next_in + strm->avail_in) - state->in); + copy = state->size - have; + if (copy > len) + copy = len; + memcpy(state->in + have, buf, copy); + strm->avail_in += copy; + state->x.pos += copy; + buf = (const char *)buf + copy; + len -= copy; if (len && gz_comp(state, Z_NO_FLUSH) == -1) return 0; } while (len); @@ -207,8 +231,8 @@ int ZEXPORT gzwrite(file, buf, len) /* directly compress user buffer to file */ strm->avail_in = len; - strm->next_in = (voidp)buf; - state->pos += len; + strm->next_in = (z_const Bytef *)buf; + state->x.pos += len; if (gz_comp(state, Z_NO_FLUSH) == -1) return 0; } @@ -222,6 +246,7 @@ int ZEXPORT gzputc(file, c) gzFile file; int c; { + unsigned have; unsigned char buf[1]; gz_statep state; z_streamp strm; @@ -245,19 +270,23 @@ int ZEXPORT gzputc(file, c) /* try writing to input buffer for speed (state->size == 0 if buffer not initialized) */ - if (strm->avail_in < state->size) { + if (state->size) { if (strm->avail_in == 0) strm->next_in = state->in; - strm->next_in[strm->avail_in++] = c; - state->pos++; - return c; + have = (unsigned)((strm->next_in + strm->avail_in) - state->in); + if (have < state->size) { + state->in[have] = c; + strm->avail_in++; + state->x.pos++; + return c & 0xff; + } } /* no room in buffer or not initialized, use gz_write() */ buf[0] = c; if (gzwrite(file, buf, 1) != 1) return -1; - return c; + return c & 0xff; } /* -- see zlib.h -- */ @@ -274,16 +303,15 @@ int ZEXPORT gzputs(file, str) return ret == 0 && len != 0 ? -1 : ret; } -#ifdef STDC +#if defined(STDC) || defined(Z_HAVE_STDARG_H) #include /* -- see zlib.h -- */ -int ZEXPORTVA gzprintf (gzFile file, const char *format, ...) +int ZEXPORTVA gzvprintf(gzFile file, const char *format, va_list va) { int size, len; gz_statep state; z_streamp strm; - va_list va; /* get internal structure */ if (file == NULL) @@ -313,25 +341,20 @@ int ZEXPORTVA gzprintf (gzFile file, const char *format, ...) /* do the printf() into the input buffer, put length in len */ size = (int)(state->size); state->in[size - 1] = 0; - va_start(va, format); #ifdef NO_vsnprintf # ifdef HAS_vsprintf_void - (void)vsprintf(state->in, format, va); - va_end(va); + (void)vsprintf((char *)(state->in), format, va); for (len = 0; len < size; len++) if (state->in[len] == 0) break; # else - len = vsprintf(state->in, format, va); - va_end(va); + len = vsprintf((char *)(state->in), format, va); # endif #else # ifdef HAS_vsnprintf_void - (void)vsnprintf(state->in, size, format, va); - va_end(va); - len = strlen(state->in); + (void)vsnprintf((char *)(state->in), size, format, va); + len = strlen((char *)(state->in)); # else len = vsnprintf((char *)(state->in), size, format, va); - va_end(va); # endif #endif @@ -342,11 +365,22 @@ int ZEXPORTVA gzprintf (gzFile file, const char *format, ...) /* update buffer and position, defer compression until needed */ strm->avail_in = (unsigned)len; strm->next_in = state->in; - state->pos += len; + state->x.pos += len; return len; } -#else /* !STDC */ +int ZEXPORTVA gzprintf(gzFile file, const char *format, ...) +{ + va_list va; + int ret; + + va_start(va, format); + ret = gzvprintf(file, format, va); + va_end(va); + return ret; +} + +#else /* !STDC && !Z_HAVE_STDARG_H */ /* -- see zlib.h -- */ int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, @@ -366,6 +400,10 @@ int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, state = (gz_statep)file; strm = &(state->strm); + /* check that can really pass pointer in ints */ + if (sizeof(int) != sizeof(void *)) + return 0; + /* check that we're writing and that there's no error */ if (state->mode != GZ_WRITE || state->err != Z_OK) return 0; @@ -390,22 +428,23 @@ int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, state->in[size - 1] = 0; #ifdef NO_snprintf # ifdef HAS_sprintf_void - sprintf(state->in, format, a1, a2, a3, a4, a5, a6, a7, a8, + sprintf((char *)(state->in), format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); for (len = 0; len < size; len++) if (state->in[len] == 0) break; # else - len = sprintf(state->in, format, a1, a2, a3, a4, a5, a6, a7, a8, - a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); + len = sprintf((char *)(state->in), format, a1, a2, a3, a4, a5, a6, a7, a8, + a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); # endif #else # ifdef HAS_snprintf_void - snprintf(state->in, size, format, a1, a2, a3, a4, a5, a6, a7, a8, + snprintf((char *)(state->in), size, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); - len = strlen(state->in); + len = strlen((char *)(state->in)); # else - len = snprintf(state->in, size, format, a1, a2, a3, a4, a5, a6, a7, a8, - a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); + len = snprintf((char *)(state->in), size, format, a1, a2, a3, a4, a5, a6, + a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, + a19, a20); # endif #endif @@ -416,7 +455,7 @@ int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, /* update buffer and position, defer compression until needed */ strm->avail_in = (unsigned)len; strm->next_in = state->in; - state->pos += len; + state->x.pos += len; return len; } @@ -500,7 +539,7 @@ int ZEXPORT gzsetparams(file, level, strategy) int ZEXPORT gzclose_w(file) gzFile file; { - int ret = 0; + int ret = Z_OK; gz_statep state; /* get internal structure */ @@ -515,17 +554,24 @@ int ZEXPORT gzclose_w(file) /* check for seek request */ if (state->seek) { state->seek = 0; - ret += gz_zero(state, state->skip); + if (gz_zero(state, state->skip) == -1) + ret = state->err; } /* flush, free memory, and close file */ - ret += gz_comp(state, Z_FINISH); - (void)deflateEnd(&(state->strm)); - free(state->out); - free(state->in); + if (gz_comp(state, Z_FINISH) == -1) + ret = state->err; + if (state->size) { + if (!state->direct) { + (void)deflateEnd(&(state->strm)); + free(state->out); + } + free(state->in); + } gz_error(state, Z_OK, NULL); free(state->path); - ret += close(state->fd); + if (close(state->fd) == -1) + ret = Z_ERRNO; free(state); - return ret ? Z_ERRNO : Z_OK; + return ret; } diff --git a/third_party/zlib/infback.c b/deps/src/zlib/infback.c similarity index 98% rename from third_party/zlib/infback.c rename to deps/src/zlib/infback.c index af3a8c965..f3833c2e4 100644 --- a/third_party/zlib/infback.c +++ b/deps/src/zlib/infback.c @@ -1,5 +1,5 @@ /* infback.c -- inflate using a call-back interface - * Copyright (C) 1995-2009 Mark Adler + * Copyright (C) 1995-2011 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -42,10 +42,19 @@ int stream_size; return Z_STREAM_ERROR; strm->msg = Z_NULL; /* in case we return an error */ if (strm->zalloc == (alloc_func)0) { +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else strm->zalloc = zcalloc; strm->opaque = (voidpf)0; +#endif } - if (strm->zfree == (free_func)0) strm->zfree = zcfree; + if (strm->zfree == (free_func)0) +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zfree = zcfree; +#endif state = (struct inflate_state FAR *)ZALLOC(strm, 1, sizeof(struct inflate_state)); if (state == Z_NULL) return Z_MEM_ERROR; @@ -246,7 +255,7 @@ out_func out; void FAR *out_desc; { struct inflate_state FAR *state; - unsigned char FAR *next; /* next input */ + z_const unsigned char FAR *next; /* next input */ unsigned char FAR *put; /* next output */ unsigned have, left; /* available input and output */ unsigned long hold; /* bit buffer */ @@ -394,7 +403,6 @@ void FAR *out_desc; PULLBYTE(); } if (here.val < 16) { - NEEDBITS(here.bits); DROPBITS(here.bits); state->lens[state->have++] = here.val; } diff --git a/third_party/zlib/inffast.c b/deps/src/zlib/inffast.c similarity index 98% rename from third_party/zlib/inffast.c rename to deps/src/zlib/inffast.c index 2f1d60b43..bda59ceb6 100644 --- a/third_party/zlib/inffast.c +++ b/deps/src/zlib/inffast.c @@ -1,5 +1,5 @@ /* inffast.c -- fast decoding - * Copyright (C) 1995-2008, 2010 Mark Adler + * Copyright (C) 1995-2008, 2010, 2013 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -69,8 +69,8 @@ z_streamp strm; unsigned start; /* inflate()'s starting value for strm->avail_out */ { struct inflate_state FAR *state; - unsigned char FAR *in; /* local strm->next_in */ - unsigned char FAR *last; /* while in < last, enough input available */ + z_const unsigned char FAR *in; /* local strm->next_in */ + z_const unsigned char FAR *last; /* have enough input while in < last */ unsigned char FAR *out; /* local strm->next_out */ unsigned char FAR *beg; /* inflate()'s initial strm->next_out */ unsigned char FAR *end; /* while out < end, enough space available */ diff --git a/third_party/zlib/inffast.h b/deps/src/zlib/inffast.h similarity index 100% rename from third_party/zlib/inffast.h rename to deps/src/zlib/inffast.h diff --git a/third_party/zlib/inffixed.h b/deps/src/zlib/inffixed.h similarity index 96% rename from third_party/zlib/inffixed.h rename to deps/src/zlib/inffixed.h index 75ed4b597..d62832776 100644 --- a/third_party/zlib/inffixed.h +++ b/deps/src/zlib/inffixed.h @@ -2,9 +2,9 @@ * Generated automatically by makefixed(). */ - /* WARNING: this file should *not* be used by applications. It - is part of the implementation of the compression library and - is subject to change. Applications should only use zlib.h. + /* WARNING: this file should *not* be used by applications. + It is part of the implementation of this library and is + subject to change. Applications should only use zlib.h. */ static const code lenfix[512] = { diff --git a/third_party/zlib/inflate.c b/deps/src/zlib/inflate.c similarity index 94% rename from third_party/zlib/inflate.c rename to deps/src/zlib/inflate.c index a8431abea..870f89bb4 100644 --- a/third_party/zlib/inflate.c +++ b/deps/src/zlib/inflate.c @@ -1,5 +1,5 @@ /* inflate.c -- zlib decompression - * Copyright (C) 1995-2010 Mark Adler + * Copyright (C) 1995-2012 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -93,14 +93,15 @@ /* function prototypes */ local void fixedtables OF((struct inflate_state FAR *state)); -local int updatewindow OF((z_streamp strm, unsigned out)); +local int updatewindow OF((z_streamp strm, const unsigned char FAR *end, + unsigned copy)); #ifdef BUILDFIXED void makefixed OF((void)); #endif -local unsigned syncsearch OF((unsigned FAR *have, unsigned char FAR *buf, +local unsigned syncsearch OF((unsigned FAR *have, const unsigned char FAR *buf, unsigned len)); -int ZEXPORT inflateReset(strm) +int ZEXPORT inflateResetKeep(strm) z_streamp strm; { struct inflate_state FAR *state; @@ -109,15 +110,13 @@ z_streamp strm; state = (struct inflate_state FAR *)strm->state; strm->total_in = strm->total_out = state->total = 0; strm->msg = Z_NULL; - strm->adler = 1; /* to support ill-conceived Java test suite */ + if (state->wrap) /* to support ill-conceived Java test suite */ + strm->adler = state->wrap & 1; state->mode = HEAD; state->last = 0; state->havedict = 0; state->dmax = 32768U; state->head = Z_NULL; - state->wsize = 0; - state->whave = 0; - state->wnext = 0; state->hold = 0; state->bits = 0; state->lencode = state->distcode = state->next = state->codes; @@ -127,6 +126,19 @@ z_streamp strm; return Z_OK; } +int ZEXPORT inflateReset(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + state->wsize = 0; + state->whave = 0; + state->wnext = 0; + return inflateResetKeep(strm); +} + int ZEXPORT inflateReset2(strm, windowBits) z_streamp strm; int windowBits; @@ -180,10 +192,19 @@ int stream_size; if (strm == Z_NULL) return Z_STREAM_ERROR; strm->msg = Z_NULL; /* in case we return an error */ if (strm->zalloc == (alloc_func)0) { +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else strm->zalloc = zcalloc; strm->opaque = (voidpf)0; +#endif } - if (strm->zfree == (free_func)0) strm->zfree = zcfree; + if (strm->zfree == (free_func)0) +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zfree = zcfree; +#endif state = (struct inflate_state FAR *) ZALLOC(strm, 1, sizeof(struct inflate_state)); if (state == Z_NULL) return Z_MEM_ERROR; @@ -321,8 +342,8 @@ void makefixed() low = 0; for (;;) { if ((low % 7) == 0) printf("\n "); - printf("{%u,%u,%d}", state.lencode[low].op, state.lencode[low].bits, - state.lencode[low].val); + printf("{%u,%u,%d}", (low & 127) == 99 ? 64 : state.lencode[low].op, + state.lencode[low].bits, state.lencode[low].val); if (++low == size) break; putchar(','); } @@ -355,12 +376,13 @@ void makefixed() output will fall in the output data, making match copies simpler and faster. The advantage may be dependent on the size of the processor's data caches. */ -local int updatewindow(strm, out) +local int updatewindow(strm, end, copy) z_streamp strm; -unsigned out; +const Bytef *end; +unsigned copy; { struct inflate_state FAR *state; - unsigned copy, dist; + unsigned dist; state = (struct inflate_state FAR *)strm->state; @@ -380,19 +402,18 @@ unsigned out; } /* copy state->wsize or less output bytes into the circular window */ - copy = out - strm->avail_out; if (copy >= state->wsize) { - zmemcpy(state->window, strm->next_out - state->wsize, state->wsize); + zmemcpy(state->window, end - state->wsize, state->wsize); state->wnext = 0; state->whave = state->wsize; } else { dist = state->wsize - state->wnext; if (dist > copy) dist = copy; - zmemcpy(state->window + state->wnext, strm->next_out - copy, dist); + zmemcpy(state->window + state->wnext, end - copy, dist); copy -= dist; if (copy) { - zmemcpy(state->window, strm->next_out - copy, copy); + zmemcpy(state->window, end - copy, copy); state->wnext = copy; state->whave = state->wsize; } @@ -499,11 +520,6 @@ unsigned out; bits -= bits & 7; \ } while (0) -/* Reverse the bytes in a 32-bit value */ -#define REVERSE(q) \ - ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \ - (((q) & 0xff00) << 8) + (((q) & 0xff) << 24)) - /* inflate() uses a state machine to process as much input data and generate as much output data as possible before returning. The state machine is @@ -591,7 +607,7 @@ z_streamp strm; int flush; { struct inflate_state FAR *state; - unsigned char FAR *next; /* next input */ + z_const unsigned char FAR *next; /* next input */ unsigned char FAR *put; /* next output */ unsigned have, left; /* available input and output */ unsigned long hold; /* bit buffer */ @@ -797,7 +813,7 @@ int flush; #endif case DICTID: NEEDBITS(32); - strm->adler = state->check = REVERSE(hold); + strm->adler = state->check = ZSWAP32(hold); INITBITS(); state->mode = DICT; case DICT: @@ -905,7 +921,7 @@ int flush; while (state->have < 19) state->lens[order[state->have++]] = 0; state->next = state->codes; - state->lencode = (code const FAR *)(state->next); + state->lencode = (const code FAR *)(state->next); state->lenbits = 7; ret = inflate_table(CODES, state->lens, 19, &(state->next), &(state->lenbits), state->work); @@ -925,7 +941,6 @@ int flush; PULLBYTE(); } if (here.val < 16) { - NEEDBITS(here.bits); DROPBITS(here.bits); state->lens[state->have++] = here.val; } @@ -980,7 +995,7 @@ int flush; values here (9 and 6) without reading the comments in inftrees.h concerning the ENOUGH constants, which depend on those values */ state->next = state->codes; - state->lencode = (code const FAR *)(state->next); + state->lencode = (const code FAR *)(state->next); state->lenbits = 9; ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), &(state->lenbits), state->work); @@ -989,7 +1004,7 @@ int flush; state->mode = BAD; break; } - state->distcode = (code const FAR *)(state->next); + state->distcode = (const code FAR *)(state->next); state->distbits = 6; ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, &(state->next), &(state->distbits), state->work); @@ -1170,7 +1185,7 @@ int flush; #ifdef GUNZIP state->flags ? hold : #endif - REVERSE(hold)) != state->check) { + ZSWAP32(hold)) != state->check) { strm->msg = (char *)"incorrect data check"; state->mode = BAD; break; @@ -1214,8 +1229,9 @@ int flush; */ inf_leave: RESTORE(); - if (state->wsize || (state->mode < CHECK && out != strm->avail_out)) - if (updatewindow(strm, out)) { + if (state->wsize || (out != strm->avail_out && state->mode < BAD && + (state->mode < CHECK || flush != Z_FINISH))) + if (updatewindow(strm, strm->next_out, out - strm->avail_out)) { state->mode = MEM; return Z_MEM_ERROR; } @@ -1249,13 +1265,37 @@ z_streamp strm; return Z_OK; } +int ZEXPORT inflateGetDictionary(strm, dictionary, dictLength) +z_streamp strm; +Bytef *dictionary; +uInt *dictLength; +{ + struct inflate_state FAR *state; + + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + + /* copy dictionary */ + if (state->whave && dictionary != Z_NULL) { + zmemcpy(dictionary, state->window + state->wnext, + state->whave - state->wnext); + zmemcpy(dictionary + state->whave - state->wnext, + state->window, state->wnext); + } + if (dictLength != Z_NULL) + *dictLength = state->whave; + return Z_OK; +} + int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength) z_streamp strm; const Bytef *dictionary; uInt dictLength; { struct inflate_state FAR *state; - unsigned long id; + unsigned long dictid; + int ret; /* check state */ if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; @@ -1263,29 +1303,21 @@ uInt dictLength; if (state->wrap != 0 && state->mode != DICT) return Z_STREAM_ERROR; - /* check for correct dictionary id */ + /* check for correct dictionary identifier */ if (state->mode == DICT) { - id = adler32(0L, Z_NULL, 0); - id = adler32(id, dictionary, dictLength); - if (id != state->check) + dictid = adler32(0L, Z_NULL, 0); + dictid = adler32(dictid, dictionary, dictLength); + if (dictid != state->check) return Z_DATA_ERROR; } - /* copy dictionary to window */ - if (updatewindow(strm, strm->avail_out)) { + /* copy dictionary to window using updatewindow(), which will amend the + existing dictionary if appropriate */ + ret = updatewindow(strm, dictionary + dictLength, dictLength); + if (ret) { state->mode = MEM; return Z_MEM_ERROR; } - if (dictLength > state->wsize) { - zmemcpy(state->window, dictionary + dictLength - state->wsize, - state->wsize); - state->whave = state->wsize; - } - else { - zmemcpy(state->window + state->wsize - dictLength, dictionary, - dictLength); - state->whave = dictLength; - } state->havedict = 1; Tracev((stderr, "inflate: dictionary set\n")); return Z_OK; @@ -1321,7 +1353,7 @@ gz_headerp head; */ local unsigned syncsearch(have, buf, len) unsigned FAR *have; -unsigned char FAR *buf; +const unsigned char FAR *buf; unsigned len; { unsigned got; @@ -1433,8 +1465,8 @@ z_streamp source; } /* copy state */ - zmemcpy(dest, source, sizeof(z_stream)); - zmemcpy(copy, state, sizeof(struct inflate_state)); + zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream)); + zmemcpy((voidpf)copy, (voidpf)state, sizeof(struct inflate_state)); if (state->lencode >= state->codes && state->lencode <= state->codes + ENOUGH - 1) { copy->lencode = copy->codes + (state->lencode - state->codes); diff --git a/third_party/zlib/inflate.h b/deps/src/zlib/inflate.h similarity index 100% rename from third_party/zlib/inflate.h rename to deps/src/zlib/inflate.h diff --git a/third_party/zlib/inftrees.c b/deps/src/zlib/inftrees.c similarity index 88% rename from third_party/zlib/inftrees.c rename to deps/src/zlib/inftrees.c index 11e9c52ac..44d89cf24 100644 --- a/third_party/zlib/inftrees.c +++ b/deps/src/zlib/inftrees.c @@ -1,5 +1,5 @@ /* inftrees.c -- generate Huffman trees for efficient decoding - * Copyright (C) 1995-2010 Mark Adler + * Copyright (C) 1995-2013 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -9,7 +9,7 @@ #define MAXBITS 15 const char inflate_copyright[] = - " inflate 1.2.5 Copyright 1995-2010 Mark Adler "; + " inflate 1.2.8 Copyright 1995-2013 Mark Adler "; /* If you use the zlib library in a product, an acknowledgment is welcome in the documentation of your product. If for some reason you cannot @@ -62,7 +62,7 @@ unsigned short FAR *work; 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; static const unsigned short lext[31] = { /* Length codes 257..285 extra */ 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, - 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 73, 195}; + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78}; static const unsigned short dbase[32] = { /* Distance codes 0..29 base */ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, @@ -208,8 +208,8 @@ unsigned short FAR *work; mask = used - 1; /* mask for comparing low */ /* check available table space */ - if ((type == LENS && used >= ENOUGH_LENS) || - (type == DISTS && used >= ENOUGH_DISTS)) + if ((type == LENS && used > ENOUGH_LENS) || + (type == DISTS && used > ENOUGH_DISTS)) return 1; /* process all codes and make table entries */ @@ -277,8 +277,8 @@ unsigned short FAR *work; /* check for enough space */ used += 1U << curr; - if ((type == LENS && used >= ENOUGH_LENS) || - (type == DISTS && used >= ENOUGH_DISTS)) + if ((type == LENS && used > ENOUGH_LENS) || + (type == DISTS && used > ENOUGH_DISTS)) return 1; /* point entry in root table to sub-table */ @@ -289,38 +289,14 @@ unsigned short FAR *work; } } - /* - Fill in rest of table for incomplete codes. This loop is similar to the - loop above in incrementing huff for table indices. It is assumed that - len is equal to curr + drop, so there is no loop needed to increment - through high index bits. When the current sub-table is filled, the loop - drops back to the root table to fill in any remaining entries there. - */ - here.op = (unsigned char)64; /* invalid code marker */ - here.bits = (unsigned char)(len - drop); - here.val = (unsigned short)0; - while (huff != 0) { - /* when done with sub-table, drop back to root table */ - if (drop != 0 && (huff & mask) != low) { - drop = 0; - len = root; - next = *table; - here.bits = (unsigned char)len; - } - - /* put invalid code marker in table */ - next[huff >> drop] = here; - - /* backwards increment the len-bit code huff */ - incr = 1U << (len - 1); - while (huff & incr) - incr >>= 1; - if (incr != 0) { - huff &= incr - 1; - huff += incr; - } - else - huff = 0; + /* fill in remaining table entry if code is incomplete (guaranteed to have + at most one remaining entry, since if the code is incomplete, the + maximum code length that was allowed to get this far is one bit) */ + if (huff != 0) { + here.op = (unsigned char)64; /* invalid code marker */ + here.bits = (unsigned char)(len - drop); + here.val = (unsigned short)0; + next[huff] = here; } /* set return parameters */ diff --git a/third_party/zlib/inftrees.h b/deps/src/zlib/inftrees.h similarity index 100% rename from third_party/zlib/inftrees.h rename to deps/src/zlib/inftrees.h diff --git a/third_party/zlib/trees.c b/deps/src/zlib/trees.c similarity index 96% rename from third_party/zlib/trees.c rename to deps/src/zlib/trees.c index 56e9bb1c1..1fd7759ef 100644 --- a/third_party/zlib/trees.c +++ b/deps/src/zlib/trees.c @@ -1,5 +1,5 @@ /* trees.c -- output deflated data using Huffman coding - * Copyright (C) 1995-2010 Jean-loup Gailly + * Copyright (C) 1995-2012 Jean-loup Gailly * detect_data_type() function provided freely by Cosmin Truta, 2006 * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -74,11 +74,6 @@ local const uch bl_order[BL_CODES] * probability, to avoid transmitting the lengths for unused bit length codes. */ -#define Buf_size (8 * 2*sizeof(char)) -/* Number of bits used within bi_buf. (bi_buf might be implemented on - * more than 16 bits on some systems.) - */ - /* =========================================================================== * Local data. These are initialized only once. */ @@ -151,8 +146,8 @@ local void send_tree OF((deflate_state *s, ct_data *tree, int max_code)); local int build_bl_tree OF((deflate_state *s)); local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes, int blcodes)); -local void compress_block OF((deflate_state *s, ct_data *ltree, - ct_data *dtree)); +local void compress_block OF((deflate_state *s, const ct_data *ltree, + const ct_data *dtree)); local int detect_data_type OF((deflate_state *s)); local unsigned bi_reverse OF((unsigned value, int length)); local void bi_windup OF((deflate_state *s)); @@ -399,7 +394,6 @@ void ZLIB_INTERNAL _tr_init(s) s->bi_buf = 0; s->bi_valid = 0; - s->last_eob_len = 8; /* enough lookahead for inflate */ #ifdef DEBUG s->compressed_len = 0L; s->bits_sent = 0L; @@ -882,16 +876,18 @@ void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last) copy_block(s, buf, (unsigned)stored_len, 1); /* with header */ } +/* =========================================================================== + * Flush the bits in the bit buffer to pending output (leaves at most 7 bits) + */ +void ZLIB_INTERNAL _tr_flush_bits(s) + deflate_state *s; +{ + bi_flush(s); +} + /* =========================================================================== * Send one empty static block to give enough lookahead for inflate. * This takes 10 bits, of which 7 may remain in the bit buffer. - * The current inflate code requires 9 bits of lookahead. If the - * last two codes for the previous block (real code plus EOB) were coded - * on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode - * the last real code. In this case we send two empty static blocks instead - * of one. (There are no problems if the previous block is stored or fixed.) - * To simplify the code, we assume the worst case of last real code encoded - * on one bit only. */ void ZLIB_INTERNAL _tr_align(s) deflate_state *s; @@ -902,20 +898,6 @@ void ZLIB_INTERNAL _tr_align(s) s->compressed_len += 10L; /* 3 for block type, 7 for EOB */ #endif bi_flush(s); - /* Of the 10 bits for the empty block, we have already sent - * (10 - bi_valid) bits. The lookahead for the last real code (before - * the EOB of the previous block) was thus at least one plus the length - * of the EOB plus what we have just sent of the empty static block. - */ - if (1 + s->last_eob_len + 10 - s->bi_valid < 9) { - send_bits(s, STATIC_TREES<<1, 3); - send_code(s, END_BLOCK, static_ltree); -#ifdef DEBUG - s->compressed_len += 10L; -#endif - bi_flush(s); - } - s->last_eob_len = 7; } /* =========================================================================== @@ -990,7 +972,8 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) { #endif send_bits(s, (STATIC_TREES<<1)+last, 3); - compress_block(s, (ct_data *)static_ltree, (ct_data *)static_dtree); + compress_block(s, (const ct_data *)static_ltree, + (const ct_data *)static_dtree); #ifdef DEBUG s->compressed_len += 3 + s->static_len; #endif @@ -998,7 +981,8 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) send_bits(s, (DYN_TREES<<1)+last, 3); send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1, max_blindex+1); - compress_block(s, (ct_data *)s->dyn_ltree, (ct_data *)s->dyn_dtree); + compress_block(s, (const ct_data *)s->dyn_ltree, + (const ct_data *)s->dyn_dtree); #ifdef DEBUG s->compressed_len += 3 + s->opt_len; #endif @@ -1075,8 +1059,8 @@ int ZLIB_INTERNAL _tr_tally (s, dist, lc) */ local void compress_block(s, ltree, dtree) deflate_state *s; - ct_data *ltree; /* literal tree */ - ct_data *dtree; /* distance tree */ + const ct_data *ltree; /* literal tree */ + const ct_data *dtree; /* distance tree */ { unsigned dist; /* distance of matched string */ int lc; /* match length or unmatched char (if dist == 0) */ @@ -1118,7 +1102,6 @@ local void compress_block(s, ltree, dtree) } while (lx < s->last_lit); send_code(s, END_BLOCK, ltree); - s->last_eob_len = ltree[END_BLOCK].Len; } /* =========================================================================== @@ -1226,7 +1209,6 @@ local void copy_block(s, buf, len, header) int header; /* true if block header must be written */ { bi_windup(s); /* align on byte boundary */ - s->last_eob_len = 8; /* enough lookahead for inflate */ if (header) { put_short(s, (ush)len); diff --git a/third_party/zlib/trees.h b/deps/src/zlib/trees.h similarity index 100% rename from third_party/zlib/trees.h rename to deps/src/zlib/trees.h diff --git a/third_party/zlib/uncompr.c b/deps/src/zlib/uncompr.c similarity index 97% rename from third_party/zlib/uncompr.c rename to deps/src/zlib/uncompr.c index ad98be3a5..242e9493d 100644 --- a/third_party/zlib/uncompr.c +++ b/deps/src/zlib/uncompr.c @@ -30,7 +30,7 @@ int ZEXPORT uncompress (dest, destLen, source, sourceLen) z_stream stream; int err; - stream.next_in = (Bytef*)source; + stream.next_in = (z_const Bytef *)source; stream.avail_in = (uInt)sourceLen; /* Check for source > 64K on 16-bit machine: */ if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; diff --git a/third_party/zlib/zconf.h b/deps/src/zlib/zconf.h similarity index 72% rename from third_party/zlib/zconf.h rename to deps/src/zlib/zconf.h index 02ce56c43..9987a7755 100644 --- a/third_party/zlib/zconf.h +++ b/deps/src/zlib/zconf.h @@ -1,5 +1,5 @@ /* zconf.h -- configuration of the zlib compression library - * Copyright (C) 1995-2010 Jean-loup Gailly. + * Copyright (C) 1995-2013 Jean-loup Gailly. * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -15,11 +15,13 @@ * this permanently in zconf.h using "./configure --zprefix". */ #ifdef Z_PREFIX /* may be set to #if 1 by ./configure */ +# define Z_PREFIX_SET /* all linked symbols */ # define _dist_code z__dist_code # define _length_code z__length_code # define _tr_align z__tr_align +# define _tr_flush_bits z__tr_flush_bits # define _tr_flush_block z__tr_flush_block # define _tr_init z__tr_init # define _tr_stored_block z__tr_stored_block @@ -27,9 +29,11 @@ # define adler32 z_adler32 # define adler32_combine z_adler32_combine # define adler32_combine64 z_adler32_combine64 -# define compress z_compress -# define compress2 z_compress2 -# define compressBound z_compressBound +# ifndef Z_SOLO +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# endif # define crc32 z_crc32 # define crc32_combine z_crc32_combine # define crc32_combine64 z_crc32_combine64 @@ -40,44 +44,53 @@ # define deflateInit2_ z_deflateInit2_ # define deflateInit_ z_deflateInit_ # define deflateParams z_deflateParams +# define deflatePending z_deflatePending # define deflatePrime z_deflatePrime # define deflateReset z_deflateReset +# define deflateResetKeep z_deflateResetKeep # define deflateSetDictionary z_deflateSetDictionary # define deflateSetHeader z_deflateSetHeader # define deflateTune z_deflateTune # define deflate_copyright z_deflate_copyright # define get_crc_table z_get_crc_table -# define gz_error z_gz_error -# define gz_intmax z_gz_intmax -# define gz_strwinerror z_gz_strwinerror -# define gzbuffer z_gzbuffer -# define gzclearerr z_gzclearerr -# define gzclose z_gzclose -# define gzclose_r z_gzclose_r -# define gzclose_w z_gzclose_w -# define gzdirect z_gzdirect -# define gzdopen z_gzdopen -# define gzeof z_gzeof -# define gzerror z_gzerror -# define gzflush z_gzflush -# define gzgetc z_gzgetc -# define gzgets z_gzgets -# define gzoffset z_gzoffset -# define gzoffset64 z_gzoffset64 -# define gzopen z_gzopen -# define gzopen64 z_gzopen64 -# define gzprintf z_gzprintf -# define gzputc z_gzputc -# define gzputs z_gzputs -# define gzread z_gzread -# define gzrewind z_gzrewind -# define gzseek z_gzseek -# define gzseek64 z_gzseek64 -# define gzsetparams z_gzsetparams -# define gztell z_gztell -# define gztell64 z_gztell64 -# define gzungetc z_gzungetc -# define gzwrite z_gzwrite +# ifndef Z_SOLO +# define gz_error z_gz_error +# define gz_intmax z_gz_intmax +# define gz_strwinerror z_gz_strwinerror +# define gzbuffer z_gzbuffer +# define gzclearerr z_gzclearerr +# define gzclose z_gzclose +# define gzclose_r z_gzclose_r +# define gzclose_w z_gzclose_w +# define gzdirect z_gzdirect +# define gzdopen z_gzdopen +# define gzeof z_gzeof +# define gzerror z_gzerror +# define gzflush z_gzflush +# define gzgetc z_gzgetc +# define gzgetc_ z_gzgetc_ +# define gzgets z_gzgets +# define gzoffset z_gzoffset +# define gzoffset64 z_gzoffset64 +# define gzopen z_gzopen +# define gzopen64 z_gzopen64 +# ifdef _WIN32 +# define gzopen_w z_gzopen_w +# endif +# define gzprintf z_gzprintf +# define gzvprintf z_gzvprintf +# define gzputc z_gzputc +# define gzputs z_gzputs +# define gzread z_gzread +# define gzrewind z_gzrewind +# define gzseek z_gzseek +# define gzseek64 z_gzseek64 +# define gzsetparams z_gzsetparams +# define gztell z_gztell +# define gztell64 z_gztell64 +# define gzungetc z_gzungetc +# define gzwrite z_gzwrite +# endif # define inflate z_inflate # define inflateBack z_inflateBack # define inflateBackEnd z_inflateBackEnd @@ -92,16 +105,22 @@ # define inflateReset z_inflateReset # define inflateReset2 z_inflateReset2 # define inflateSetDictionary z_inflateSetDictionary +# define inflateGetDictionary z_inflateGetDictionary # define inflateSync z_inflateSync # define inflateSyncPoint z_inflateSyncPoint # define inflateUndermine z_inflateUndermine +# define inflateResetKeep z_inflateResetKeep # define inflate_copyright z_inflate_copyright # define inflate_fast z_inflate_fast # define inflate_table z_inflate_table -# define uncompress z_uncompress +# ifndef Z_SOLO +# define uncompress z_uncompress +# endif # define zError z_zError -# define zcalloc z_zcalloc -# define zcfree z_zcfree +# ifndef Z_SOLO +# define zcalloc z_zcalloc +# define zcfree z_zcfree +# endif # define zlibCompileFlags z_zlibCompileFlags # define zlibVersion z_zlibVersion @@ -111,7 +130,9 @@ # define alloc_func z_alloc_func # define charf z_charf # define free_func z_free_func -# define gzFile z_gzFile +# ifndef Z_SOLO +# define gzFile z_gzFile +# endif # define gz_header z_gz_header # define gz_headerp z_gz_headerp # define in_func z_in_func @@ -197,6 +218,12 @@ # endif #endif +#if defined(ZLIB_CONST) && !defined(z_const) +# define z_const const +#else +# define z_const +#endif + /* Some Mac compilers merge all .h files incorrectly: */ #if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) # define NO_DUMMY_DECL @@ -243,6 +270,14 @@ # endif #endif +#ifndef Z_ARG /* function prototypes for stdarg */ +# if defined(STDC) || defined(Z_HAVE_STDARG_H) +# define Z_ARG(args) args +# else +# define Z_ARG(args) () +# endif +#endif + /* The following definitions for FAR are needed only for MSDOS mixed * model programming (small or medium model with some far allocations). * This was tested only with MSC; for other MSDOS compilers you may have @@ -356,12 +391,47 @@ typedef uLong FAR uLongf; typedef Byte *voidp; #endif +#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC) +# include +# if (UINT_MAX == 0xffffffffUL) +# define Z_U4 unsigned +# elif (ULONG_MAX == 0xffffffffUL) +# define Z_U4 unsigned long +# elif (USHRT_MAX == 0xffffffffUL) +# define Z_U4 unsigned short +# endif +#endif + +#ifdef Z_U4 + typedef Z_U4 z_crc_t; +#else + typedef unsigned long z_crc_t; +#endif + #ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */ # define Z_HAVE_UNISTD_H #endif +#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_STDARG_H +#endif + #ifdef STDC -# include /* for off_t */ +# ifndef Z_SOLO +# include /* for off_t */ +# endif +#endif + +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifndef Z_SOLO +# include /* for va_list */ +# endif +#endif + +#ifdef _WIN32 +# ifndef Z_SOLO +# include /* for wchar_t */ +# endif #endif /* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and @@ -370,21 +440,38 @@ typedef uLong FAR uLongf; * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as * equivalently requesting no 64-bit operations */ -#if -_LARGEFILE64_SOURCE - -1 == 1 +#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1 # undef _LARGEFILE64_SOURCE #endif -#if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE) -# include /* for SEEK_* and off_t */ -# ifdef VMS -# include /* for off_t */ -# endif -# ifndef z_off_t -# define z_off_t off_t +#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H) +# define Z_HAVE_UNISTD_H +#endif +#ifndef Z_SOLO +# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE) +# include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ +# ifdef VMS +# include /* for off_t */ +# endif +# ifndef z_off_t +# define z_off_t off_t +# endif # endif #endif -#ifndef SEEK_SET +#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0 +# define Z_LFS64 +#endif + +#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64) +# define Z_LARGE64 +#endif + +#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64) +# define Z_WANT64 +#endif + +#if !defined(SEEK_SET) && !defined(Z_SOLO) # define SEEK_SET 0 /* Seek from beginning of file. */ # define SEEK_CUR 1 /* Seek from current position. */ # define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ @@ -394,18 +481,14 @@ typedef uLong FAR uLongf; # define z_off_t long #endif -#if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0 +#if !defined(_WIN32) && defined(Z_LARGE64) # define z_off64_t off64_t #else -# define z_off64_t z_off_t -#endif - -#if defined(__OS400__) -# define NO_vsnprintf -#endif - -#if defined(__MVS__) -# define NO_vsnprintf +# if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO) +# define z_off64_t __int64 +# else +# define z_off64_t z_off_t +# endif #endif /* MVS linker does not support external names larger than 8 bytes */ diff --git a/third_party/zlib/zlib.h b/deps/src/zlib/zlib.h similarity index 84% rename from third_party/zlib/zlib.h rename to deps/src/zlib/zlib.h index bfbba83e8..3e0c7672a 100644 --- a/third_party/zlib/zlib.h +++ b/deps/src/zlib/zlib.h @@ -1,7 +1,7 @@ /* zlib.h -- interface of the 'zlib' general purpose compression library - version 1.2.5, April 19th, 2010 + version 1.2.8, April 28th, 2013 - Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler + Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -24,8 +24,8 @@ The data format used by the zlib library is described by RFCs (Request for - Comments) 1950 to 1952 in the files http://www.ietf.org/rfc/rfc1950.txt - (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). + Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950 + (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). */ #ifndef ZLIB_H @@ -37,11 +37,11 @@ extern "C" { #endif -#define ZLIB_VERSION "1.2.5" -#define ZLIB_VERNUM 0x1250 +#define ZLIB_VERSION "1.2.8" +#define ZLIB_VERNUM 0x1280 #define ZLIB_VER_MAJOR 1 #define ZLIB_VER_MINOR 2 -#define ZLIB_VER_REVISION 5 +#define ZLIB_VER_REVISION 8 #define ZLIB_VER_SUBREVISION 0 /* @@ -83,15 +83,15 @@ typedef void (*free_func) OF((voidpf opaque, voidpf address)); struct internal_state; typedef struct z_stream_s { - Bytef *next_in; /* next input byte */ + z_const Bytef *next_in; /* next input byte */ uInt avail_in; /* number of bytes available at next_in */ - uLong total_in; /* total nb of input bytes read so far */ + uLong total_in; /* total number of input bytes read so far */ Bytef *next_out; /* next output byte should be put there */ uInt avail_out; /* remaining free space at next_out */ - uLong total_out; /* total nb of bytes output so far */ + uLong total_out; /* total number of bytes output so far */ - char *msg; /* last error message, NULL if no error */ + z_const char *msg; /* last error message, NULL if no error */ struct internal_state FAR *state; /* not visible by applications */ alloc_func zalloc; /* used to allocate the internal state */ @@ -327,8 +327,9 @@ ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); Z_FINISH can be used immediately after deflateInit if all the compression is to be done in a single step. In this case, avail_out must be at least the - value returned by deflateBound (see below). If deflate does not return - Z_STREAM_END, then it must be called again as described above. + value returned by deflateBound (see below). Then deflate is guaranteed to + return Z_STREAM_END. If not enough output space is provided, deflate will + not return Z_STREAM_END, and it must be called again as described above. deflate() sets strm->adler to the adler32 checksum of all input read so far (that is, total_in bytes). @@ -451,23 +452,29 @@ ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); error. However if all decompression is to be performed in a single step (a single call of inflate), the parameter flush should be set to Z_FINISH. In this case all pending input is processed and all pending output is flushed; - avail_out must be large enough to hold all the uncompressed data. (The size - of the uncompressed data may have been saved by the compressor for this - purpose.) The next operation on this stream must be inflateEnd to deallocate - the decompression state. The use of Z_FINISH is never required, but can be - used to inform inflate that a faster approach may be used for the single - inflate() call. + avail_out must be large enough to hold all of the uncompressed data for the + operation to complete. (The size of the uncompressed data may have been + saved by the compressor for this purpose.) The use of Z_FINISH is not + required to perform an inflation in one step. However it may be used to + inform inflate that a faster approach can be used for the single inflate() + call. Z_FINISH also informs inflate to not maintain a sliding window if the + stream completes, which reduces inflate's memory footprint. If the stream + does not complete, either because not all of the stream is provided or not + enough output space is provided, then a sliding window will be allocated and + inflate() can be called again to continue the operation as if Z_NO_FLUSH had + been used. In this implementation, inflate() always flushes as much output as possible to the output buffer, and always uses the faster approach on the - first call. So the only effect of the flush parameter in this implementation - is on the return value of inflate(), as noted below, or when it returns early - because Z_BLOCK or Z_TREES is used. + first call. So the effects of the flush parameter in this implementation are + on the return value of inflate() as noted below, when inflate() returns early + when Z_BLOCK or Z_TREES is used, and when inflate() avoids the allocation of + memory for a sliding window when Z_FINISH is used. If a preset dictionary is needed after this call (see inflateSetDictionary - below), inflate sets strm->adler to the adler32 checksum of the dictionary + below), inflate sets strm->adler to the Adler-32 checksum of the dictionary chosen by the compressor and returns Z_NEED_DICT; otherwise it sets - strm->adler to the adler32 checksum of all output produced so far (that is, + strm->adler to the Adler-32 checksum of all output produced so far (that is, total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described below. At the end of the stream, inflate() checks that its computed adler32 checksum is equal to that saved by the compressor and returns Z_STREAM_END @@ -478,7 +485,9 @@ ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); initializing with inflateInit2(). Any information contained in the gzip header is not retained, so applications that need that information should instead use raw inflate, see inflateInit2() below, or inflateBack() and - perform their own processing of the gzip header and trailer. + perform their own processing of the gzip header and trailer. When processing + gzip-wrapped deflate data, strm->adler32 is set to the CRC-32 of the output + producted so far. The CRC-32 is checked against the gzip trailer. inflate() returns Z_OK if some progress has been made (more input processed or more output produced), Z_STREAM_END if the end of the compressed data has @@ -580,10 +589,15 @@ ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, uInt dictLength)); /* Initializes the compression dictionary from the given byte sequence - without producing any compressed output. This function must be called - immediately after deflateInit, deflateInit2 or deflateReset, before any call - of deflate. The compressor and decompressor must use exactly the same - dictionary (see inflateSetDictionary). + without producing any compressed output. When using the zlib format, this + function must be called immediately after deflateInit, deflateInit2 or + deflateReset, and before any call of deflate. When doing raw deflate, this + function must be called either before any call of deflate, or immediately + after the completion of a deflate block, i.e. after all input has been + consumed and all output has been delivered when using any of the flush + options Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, or Z_FULL_FLUSH. The + compressor and decompressor must use exactly the same dictionary (see + inflateSetDictionary). The dictionary should consist of strings (byte sequences) that are likely to be encountered later in the data to be compressed, with the most commonly @@ -610,8 +624,8 @@ ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is inconsistent (for example if deflate has already been called for this stream - or if the compression method is bsort). deflateSetDictionary does not - perform any compression: this will be done by deflate(). + or if not at a block boundary for raw deflate). deflateSetDictionary does + not perform any compression: this will be done by deflate(). */ ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, @@ -688,8 +702,28 @@ ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, deflation of sourceLen bytes. It must be called after deflateInit() or deflateInit2(), and after deflateSetHeader(), if used. This would be used to allocate an output buffer for deflation in a single pass, and so would be - called before deflate(). -*/ + called before deflate(). If that first deflate() call is provided the + sourceLen input bytes, an output buffer allocated to the size returned by + deflateBound(), and the flush value Z_FINISH, then deflate() is guaranteed + to return Z_STREAM_END. Note that it is possible for the compressed size to + be larger than the value returned by deflateBound() if flush options other + than Z_FINISH or Z_NO_FLUSH are used. +*/ + +ZEXTERN int ZEXPORT deflatePending OF((z_streamp strm, + unsigned *pending, + int *bits)); +/* + deflatePending() returns the number of bytes and bits of output that have + been generated, but not yet provided in the available output. The bytes not + provided would be due to the available output space having being consumed. + The number of bits of output not provided are between 0 and 7, where they + await more bits to join them in order to fill out a full byte. If pending + or bits are Z_NULL, then those values are not set. + + deflatePending returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. + */ ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, int bits, @@ -703,8 +737,9 @@ ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, than or equal to 16, and that many of the least significant bits of value will be inserted in the output. - deflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source - stream state was inconsistent. + deflatePrime returns Z_OK if success, Z_BUF_ERROR if there was not enough + room in the internal buffer to insert the bits, or Z_STREAM_ERROR if the + source stream state was inconsistent. */ ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, @@ -790,10 +825,11 @@ ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, if that call returned Z_NEED_DICT. The dictionary chosen by the compressor can be determined from the adler32 value returned by that call of inflate. The compressor and decompressor must use exactly the same dictionary (see - deflateSetDictionary). For raw inflate, this function can be called - immediately after inflateInit2() or inflateReset() and before any call of - inflate() to set the dictionary. The application must insure that the - dictionary that was used for compression is provided. + deflateSetDictionary). For raw inflate, this function can be called at any + time to set the dictionary. If the provided dictionary is smaller than the + window and there is already data in the window, then the provided dictionary + will amend what's there. The application must insure that the dictionary + that was used for compression is provided. inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is @@ -803,19 +839,38 @@ ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, inflate(). */ +ZEXTERN int ZEXPORT inflateGetDictionary OF((z_streamp strm, + Bytef *dictionary, + uInt *dictLength)); +/* + Returns the sliding dictionary being maintained by inflate. dictLength is + set to the number of bytes in the dictionary, and that many bytes are copied + to dictionary. dictionary must have enough space, where 32768 bytes is + always enough. If inflateGetDictionary() is called with dictionary equal to + Z_NULL, then only the dictionary length is returned, and nothing is copied. + Similary, if dictLength is Z_NULL, then it is not set. + + inflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the + stream state is inconsistent. +*/ + ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); /* - Skips invalid compressed data until a full flush point (see above the - description of deflate with Z_FULL_FLUSH) can be found, or until all + Skips invalid compressed data until a possible full flush point (see above + for the description of deflate with Z_FULL_FLUSH) can be found, or until all available input is skipped. No output is provided. - inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR - if no more input was provided, Z_DATA_ERROR if no flush point has been - found, or Z_STREAM_ERROR if the stream structure was inconsistent. In the - success case, the application may save the current current value of total_in - which indicates where valid compressed data was found. In the error case, - the application may repeatedly call inflateSync, providing more input each - time, until success or end of the input data. + inflateSync searches for a 00 00 FF FF pattern in the compressed data. + All full flush points have this pattern, but not all occurrences of this + pattern are full flush points. + + inflateSync returns Z_OK if a possible full flush point has been found, + Z_BUF_ERROR if no more input was provided, Z_DATA_ERROR if no flush point + has been found, or Z_STREAM_ERROR if the stream structure was inconsistent. + In the success case, the application may save the current current value of + total_in which indicates where valid compressed data was found. In the + error case, the application may repeatedly call inflateSync, providing more + input each time, until success or end of the input data. */ ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, @@ -962,12 +1017,13 @@ ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, See inflateBack() for the usage of these routines. inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of - the paramaters are invalid, Z_MEM_ERROR if the internal state could not be + the parameters are invalid, Z_MEM_ERROR if the internal state could not be allocated, or Z_VERSION_ERROR if the version of the library does not match the version of the header file. */ -typedef unsigned (*in_func) OF((void FAR *, unsigned char FAR * FAR *)); +typedef unsigned (*in_func) OF((void FAR *, + z_const unsigned char FAR * FAR *)); typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, @@ -975,11 +1031,12 @@ ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, out_func out, void FAR *out_desc)); /* inflateBack() does a raw inflate with a single call using a call-back - interface for input and output. This is more efficient than inflate() for - file i/o applications in that it avoids copying between the output and the - sliding window by simply making the window itself the output buffer. This - function trusts the application to not change the output buffer passed by - the output function, at least until inflateBack() returns. + interface for input and output. This is potentially more efficient than + inflate() for file i/o applications, in that it avoids copying between the + output and the sliding window by simply making the window itself the output + buffer. inflate() can be faster on modern CPUs when used with large + buffers. inflateBack() trusts the application to not change the output + buffer passed by the output function, at least until inflateBack() returns. inflateBackInit() must be called first to allocate the internal state and to initialize the state with the user-provided window buffer. @@ -1088,6 +1145,7 @@ ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); 27-31: 0 (reserved) */ +#ifndef Z_SOLO /* utility functions */ @@ -1149,10 +1207,11 @@ ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough memory, Z_BUF_ERROR if there was not enough room in the output - buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. + buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. In + the case where there is not enough room, uncompress() will fill the output + buffer with the uncompressed data up to that point. */ - /* gzip file access functions */ /* @@ -1162,7 +1221,7 @@ ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, wrapper, documented in RFC 1952, wrapped around a deflate stream. */ -typedef voidp gzFile; /* opaque gzip file descriptor */ +typedef struct gzFile_s *gzFile; /* semi-opaque gzip file descriptor */ /* ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); @@ -1172,13 +1231,28 @@ ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); a strategy: 'f' for filtered data as in "wb6f", 'h' for Huffman-only compression as in "wb1h", 'R' for run-length encoding as in "wb1R", or 'F' for fixed code compression as in "wb9F". (See the description of - deflateInit2 for more information about the strategy parameter.) Also "a" - can be used instead of "w" to request that the gzip stream that will be - written be appended to the file. "+" will result in an error, since reading - and writing to the same gzip file is not supported. + deflateInit2 for more information about the strategy parameter.) 'T' will + request transparent writing or appending with no compression and not using + the gzip format. + + "a" can be used instead of "w" to request that the gzip stream that will + be written be appended to the file. "+" will result in an error, since + reading and writing to the same gzip file is not supported. The addition of + "x" when writing will create the file exclusively, which fails if the file + already exists. On systems that support it, the addition of "e" when + reading or writing will set the flag to close the file on an execve() call. + + These functions, as well as gzip, will read and decode a sequence of gzip + streams in a file. The append function of gzopen() can be used to create + such a file. (Also see gzflush() for another way to do this.) When + appending, gzopen does not test whether the file begins with a gzip stream, + nor does it look for the end of the gzip streams to begin appending. gzopen + will simply append a gzip stream to the existing file. gzopen can be used to read a file which is not in gzip format; in this - case gzread will directly read from the file without decompression. + case gzread will directly read from the file without decompression. When + reading, this will be detected automatically by looking for the magic two- + byte gzip header. gzopen returns NULL if the file could not be opened, if there was insufficient memory to allocate the gzFile state, or if an invalid mode was @@ -1197,7 +1271,11 @@ ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); descriptor fd, just like fclose(fdopen(fd, mode)) closes the file descriptor fd. If you want to keep fd open, use fd = dup(fd_keep); gz = gzdopen(fd, mode);. The duplicated descriptor should be saved to avoid a leak, since - gzdopen does not close fd if it fails. + gzdopen does not close fd if it fails. If you are using fileno() to get the + file descriptor from a FILE *, then you will have to use dup() to avoid + double-close()ing the file descriptor. Both gzclose() and fclose() will + close the associated file descriptor, so they need to have different file + descriptors. gzdopen returns NULL if there was insufficient memory to allocate the gzFile state, if an invalid mode was specified (an 'r', 'w', or 'a' was not @@ -1235,14 +1313,26 @@ ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); /* Reads the given number of uncompressed bytes from the compressed file. If - the input file was not in gzip format, gzread copies the given number of - bytes into the buffer. + the input file is not in gzip format, gzread copies the given number of + bytes into the buffer directly from the file. After reaching the end of a gzip stream in the input, gzread will continue - to read, looking for another gzip stream, or failing that, reading the rest - of the input file directly without decompression. The entire input file - will be read if gzread is called until it returns less than the requested - len. + to read, looking for another gzip stream. Any number of gzip streams may be + concatenated in the input file, and will all be decompressed by gzread(). + If something other than a gzip stream is encountered after a gzip stream, + that remaining trailing garbage is ignored (and no error is returned). + + gzread can be used to read a gzip file that is being concurrently written. + Upon reaching the end of the input, gzread will return with the available + data. If the error code returned by gzerror is Z_OK or Z_BUF_ERROR, then + gzclearerr can be used to clear the end of file indicator in order to permit + gzread to be tried again. Z_OK indicates that a gzip stream was completed + on the last gzread. Z_BUF_ERROR indicates that the input file ended in the + middle of a gzip stream. Note that gzread does not return -1 in the event + of an incomplete gzip stream. This error is deferred until gzclose(), which + will return Z_BUF_ERROR if the last gzread ended in the middle of a gzip + stream. Alternatively, gzerror can be used before gzclose to detect this + case. gzread returns the number of uncompressed bytes actually read, less than len for end of file, or -1 for error. @@ -1256,7 +1346,7 @@ ZEXTERN int ZEXPORT gzwrite OF((gzFile file, error. */ -ZEXTERN int ZEXPORTVA gzprintf OF((gzFile file, const char *format, ...)); +ZEXTERN int ZEXPORTVA gzprintf Z_ARG((gzFile file, const char *format, ...)); /* Converts, formats, and writes the arguments to the compressed file under control of the format string, as in fprintf. gzprintf returns the number of @@ -1301,7 +1391,10 @@ ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); /* Reads one byte from the compressed file. gzgetc returns this byte or -1 - in case of end of file or error. + in case of end of file or error. This is implemented as a macro for speed. + As such, it does not do all of the checking the other functions do. I.e. + it does not check to see if file is NULL, nor whether the structure file + points to has been clobbered or not. */ ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); @@ -1397,9 +1490,7 @@ ZEXTERN int ZEXPORT gzeof OF((gzFile file)); ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); /* Returns true (1) if file is being copied directly while reading, or false - (0) if file is a gzip stream being decompressed. This state can change from - false to true while reading the input file if the end of a gzip stream is - reached, but is followed by data that is not another gzip stream. + (0) if file is a gzip stream being decompressed. If the input file is empty, gzdirect() will return true, since the input does not contain a gzip stream. @@ -1408,6 +1499,13 @@ ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); cause buffers to be allocated to allow reading the file to determine if it is a gzip file. Therefore if gzbuffer() is used, it should be called before gzdirect(). + + When writing, gzdirect() returns true (1) if transparent writing was + requested ("wT" for the gzopen() mode), or false (0) otherwise. (Note: + gzdirect() is not needed when writing. Transparent writing must be + explicitly requested, so the application already knows the answer. When + linking statically, using gzdirect() will include all of the zlib code for + gzip file reading and decompression, which may not be desired.) */ ZEXTERN int ZEXPORT gzclose OF((gzFile file)); @@ -1419,7 +1517,8 @@ ZEXTERN int ZEXPORT gzclose OF((gzFile file)); must not be called more than once on the same allocation. gzclose will return Z_STREAM_ERROR if file is not valid, Z_ERRNO on a - file operation error, or Z_OK on success. + file operation error, Z_MEM_ERROR if out of memory, Z_BUF_ERROR if the + last read ended in the middle of a gzip stream, or Z_OK on success. */ ZEXTERN int ZEXPORT gzclose_r OF((gzFile file)); @@ -1457,6 +1556,7 @@ ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); file that is being written concurrently. */ +#endif /* !Z_SOLO */ /* checksum functions */ @@ -1492,16 +1592,17 @@ ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of - seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. + seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. Note + that the z_off_t type (like off_t) is a signed integer. If len2 is + negative, the result has no meaning or utility. */ ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); /* Update a running CRC-32 with the bytes buf[0..len-1] and return the updated CRC-32. If buf is Z_NULL, this function returns the required - initial value for the for the crc. Pre- and post-conditioning (one's - complement) is performed within this function so it shouldn't be done by the - application. + initial value for the crc. Pre- and post-conditioning (one's complement) is + performed within this function so it shouldn't be done by the application. Usage example: @@ -1544,17 +1645,42 @@ ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, const char *version, int stream_size)); #define deflateInit(strm, level) \ - deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) + deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream)) #define inflateInit(strm) \ - inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) + inflateInit_((strm), ZLIB_VERSION, (int)sizeof(z_stream)) #define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ - (strategy), ZLIB_VERSION, sizeof(z_stream)) + (strategy), ZLIB_VERSION, (int)sizeof(z_stream)) #define inflateInit2(strm, windowBits) \ - inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) + inflateInit2_((strm), (windowBits), ZLIB_VERSION, \ + (int)sizeof(z_stream)) #define inflateBackInit(strm, windowBits, window) \ inflateBackInit_((strm), (windowBits), (window), \ - ZLIB_VERSION, sizeof(z_stream)) + ZLIB_VERSION, (int)sizeof(z_stream)) + +#ifndef Z_SOLO + +/* gzgetc() macro and its supporting function and exposed data structure. Note + * that the real internal state is much larger than the exposed structure. + * This abbreviated structure exposes just enough for the gzgetc() macro. The + * user should not mess with these exposed elements, since their names or + * behavior could change in the future, perhaps even capriciously. They can + * only be used by the gzgetc() macro. You have been warned. + */ +struct gzFile_s { + unsigned have; + unsigned char *next; + z_off64_t pos; +}; +ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */ +#ifdef Z_PREFIX_SET +# undef z_gzgetc +# define z_gzgetc(g) \ + ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : gzgetc(g)) +#else +# define gzgetc(g) \ + ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : gzgetc(g)) +#endif /* provide 64-bit offset functions if _LARGEFILE64_SOURCE defined, and/or * change the regular functions to 64 bits if _FILE_OFFSET_BITS is 64 (if @@ -1562,7 +1688,7 @@ ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, * functions are changed to 64 bits) -- in case these are set on systems * without large file support, _LFS64_LARGEFILE must also be true */ -#if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0 +#ifdef Z_LARGE64 ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int)); ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile)); @@ -1571,14 +1697,23 @@ ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off64_t)); #endif -#if !defined(ZLIB_INTERNAL) && _FILE_OFFSET_BITS-0 == 64 && _LFS64_LARGEFILE-0 -# define gzopen gzopen64 -# define gzseek gzseek64 -# define gztell gztell64 -# define gzoffset gzoffset64 -# define adler32_combine adler32_combine64 -# define crc32_combine crc32_combine64 -# ifdef _LARGEFILE64_SOURCE +#if !defined(ZLIB_INTERNAL) && defined(Z_WANT64) +# ifdef Z_PREFIX_SET +# define z_gzopen z_gzopen64 +# define z_gzseek z_gzseek64 +# define z_gztell z_gztell64 +# define z_gzoffset z_gzoffset64 +# define z_adler32_combine z_adler32_combine64 +# define z_crc32_combine z_crc32_combine64 +# else +# define gzopen gzopen64 +# define gzseek gzseek64 +# define gztell gztell64 +# define gzoffset gzoffset64 +# define adler32_combine adler32_combine64 +# define crc32_combine crc32_combine64 +# endif +# ifndef Z_LARGE64 ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); ZEXTERN z_off_t ZEXPORT gzseek64 OF((gzFile, z_off_t, int)); ZEXTERN z_off_t ZEXPORT gztell64 OF((gzFile)); @@ -1595,6 +1730,13 @@ ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); #endif +#else /* Z_SOLO */ + + ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); + +#endif /* !Z_SOLO */ + /* hack for buggy compilers */ #if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL) struct internal_state {int dummy;}; @@ -1603,8 +1745,21 @@ ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, /* undocumented functions */ ZEXTERN const char * ZEXPORT zError OF((int)); ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp)); -ZEXTERN const uLongf * ZEXPORT get_crc_table OF((void)); +ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table OF((void)); ZEXTERN int ZEXPORT inflateUndermine OF((z_streamp, int)); +ZEXTERN int ZEXPORT inflateResetKeep OF((z_streamp)); +ZEXTERN int ZEXPORT deflateResetKeep OF((z_streamp)); +#if defined(_WIN32) && !defined(Z_SOLO) +ZEXTERN gzFile ZEXPORT gzopen_w OF((const wchar_t *path, + const char *mode)); +#endif +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifndef Z_SOLO +ZEXTERN int ZEXPORTVA gzvprintf Z_ARG((gzFile file, + const char *format, + va_list va)); +# endif +#endif #ifdef __cplusplus } diff --git a/third_party/zlib/zutil.c b/deps/src/zlib/zutil.c similarity index 94% rename from third_party/zlib/zutil.c rename to deps/src/zlib/zutil.c index 898ed345b..23d2ebef0 100644 --- a/third_party/zlib/zutil.c +++ b/deps/src/zlib/zutil.c @@ -1,17 +1,20 @@ /* zutil.c -- target dependent utility functions for the compression library - * Copyright (C) 1995-2005, 2010 Jean-loup Gailly. + * Copyright (C) 1995-2005, 2010, 2011, 2012 Jean-loup Gailly. * For conditions of distribution and use, see copyright notice in zlib.h */ /* @(#) $Id$ */ #include "zutil.h" +#ifndef Z_SOLO +# include "gzguts.h" +#endif #ifndef NO_DUMMY_DECL struct internal_state {int dummy;}; /* for buggy compilers */ #endif -const char * const z_errmsg[10] = { +z_const char * const z_errmsg[10] = { "need dictionary", /* Z_NEED_DICT 2 */ "stream end", /* Z_STREAM_END 1 */ "", /* Z_OK 0 */ @@ -85,27 +88,27 @@ uLong ZEXPORT zlibCompileFlags() #ifdef FASTEST flags += 1L << 21; #endif -#ifdef STDC +#if defined(STDC) || defined(Z_HAVE_STDARG_H) # ifdef NO_vsnprintf - flags += 1L << 25; + flags += 1L << 25; # ifdef HAS_vsprintf_void - flags += 1L << 26; + flags += 1L << 26; # endif # else # ifdef HAS_vsnprintf_void - flags += 1L << 26; + flags += 1L << 26; # endif # endif #else - flags += 1L << 24; + flags += 1L << 24; # ifdef NO_snprintf - flags += 1L << 25; + flags += 1L << 25; # ifdef HAS_sprintf_void - flags += 1L << 26; + flags += 1L << 26; # endif # else # ifdef HAS_snprintf_void - flags += 1L << 26; + flags += 1L << 26; # endif # endif #endif @@ -181,6 +184,7 @@ void ZLIB_INTERNAL zmemzero(dest, len) } #endif +#ifndef Z_SOLO #ifdef SYS16BIT @@ -316,3 +320,5 @@ void ZLIB_INTERNAL zcfree (opaque, ptr) } #endif /* MY_ZCALLOC */ + +#endif /* !Z_SOLO */ diff --git a/third_party/zlib/zutil.h b/deps/src/zlib/zutil.h similarity index 73% rename from third_party/zlib/zutil.h rename to deps/src/zlib/zutil.h index 258fa8879..24ab06b1c 100644 --- a/third_party/zlib/zutil.h +++ b/deps/src/zlib/zutil.h @@ -1,5 +1,5 @@ /* zutil.h -- internal interface and configuration of the compression library - * Copyright (C) 1995-2010 Jean-loup Gailly. + * Copyright (C) 1995-2013 Jean-loup Gailly. * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -13,7 +13,7 @@ #ifndef ZUTIL_H #define ZUTIL_H -#if ((__GNUC__-0) * 10 + __GNUC_MINOR__-0 >= 33) && !defined(NO_VIZ) +#ifdef HAVE_HIDDEN # define ZLIB_INTERNAL __attribute__((visibility ("hidden"))) #else # define ZLIB_INTERNAL @@ -21,7 +21,7 @@ #include "zlib.h" -#ifdef STDC +#if defined(STDC) && !defined(Z_SOLO) # if !(defined(_WIN32_WCE) && defined(_MSC_VER)) # include # endif @@ -29,6 +29,10 @@ # include #endif +#ifdef Z_SOLO + typedef long ptrdiff_t; /* guess -- will be caught if guess is wrong */ +#endif + #ifndef local # define local static #endif @@ -40,13 +44,13 @@ typedef unsigned short ush; typedef ush FAR ushf; typedef unsigned long ulg; -extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ +extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ /* (size given to avoid silly warnings with Visual C++) */ #define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] #define ERR_RETURN(strm,err) \ - return (strm->msg = (char*)ERR_MSG(err), (err)) + return (strm->msg = ERR_MSG(err), (err)) /* To be used only when the state is known to be valid */ /* common constants */ @@ -78,16 +82,18 @@ extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ #if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32)) # define OS_CODE 0x00 -# if defined(__TURBOC__) || defined(__BORLANDC__) -# if (__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__)) - /* Allow compilation with ANSI keywords only enabled */ - void _Cdecl farfree( void *block ); - void *_Cdecl farmalloc( unsigned long nbytes ); -# else -# include +# ifndef Z_SOLO +# if defined(__TURBOC__) || defined(__BORLANDC__) +# if (__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__)) + /* Allow compilation with ANSI keywords only enabled */ + void _Cdecl farfree( void *block ); + void *_Cdecl farmalloc( unsigned long nbytes ); +# else +# include +# endif +# else /* MSC or DJGPP */ +# include # endif -# else /* MSC or DJGPP */ -# include # endif #endif @@ -107,18 +113,20 @@ extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ #ifdef OS2 # define OS_CODE 0x06 -# ifdef M_I86 +# if defined(M_I86) && !defined(Z_SOLO) # include # endif #endif #if defined(MACOS) || defined(TARGET_OS_MAC) # define OS_CODE 0x07 -# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os -# include /* for fdopen */ -# else -# ifndef fdopen -# define fdopen(fd,mode) NULL /* No fdopen() */ +# ifndef Z_SOLO +# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os +# include /* for fdopen */ +# else +# ifndef fdopen +# define fdopen(fd,mode) NULL /* No fdopen() */ +# endif # endif # endif #endif @@ -153,14 +161,15 @@ extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ # endif #endif -#if defined(__BORLANDC__) +#if defined(__BORLANDC__) && !defined(MSDOS) #pragma warn -8004 #pragma warn -8008 #pragma warn -8066 #endif /* provide prototypes for these when building zlib without LFS */ -#if !defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0 +#if !defined(_WIN32) && \ + (!defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0) ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t)); ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t)); #endif @@ -177,42 +186,7 @@ extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ /* functions */ -#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550) -# ifndef HAVE_VSNPRINTF -# define HAVE_VSNPRINTF -# endif -#endif -#if defined(__CYGWIN__) -# ifndef HAVE_VSNPRINTF -# define HAVE_VSNPRINTF -# endif -#endif -#ifndef HAVE_VSNPRINTF -# ifdef MSDOS - /* vsnprintf may exist on some MS-DOS compilers (DJGPP?), - but for now we just assume it doesn't. */ -# define NO_vsnprintf -# endif -# ifdef __TURBOC__ -# define NO_vsnprintf -# endif -# ifdef WIN32 - /* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */ -# if !defined(vsnprintf) && !defined(NO_vsnprintf) -# if !defined(_MSC_VER) || ( defined(_MSC_VER) && _MSC_VER < 1500 ) -# define vsnprintf _vsnprintf -# endif -# endif -# endif -# ifdef __SASC -# define NO_vsnprintf -# endif -#endif -#ifdef VMS -# define NO_vsnprintf -#endif - -#if defined(pyr) +#if defined(pyr) || defined(Z_SOLO) # define NO_MEMCPY #endif #if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__) @@ -261,14 +235,19 @@ extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ # define Tracecv(c,x) #endif - -voidpf ZLIB_INTERNAL zcalloc OF((voidpf opaque, unsigned items, - unsigned size)); -void ZLIB_INTERNAL zcfree OF((voidpf opaque, voidpf ptr)); +#ifndef Z_SOLO + voidpf ZLIB_INTERNAL zcalloc OF((voidpf opaque, unsigned items, + unsigned size)); + void ZLIB_INTERNAL zcfree OF((voidpf opaque, voidpf ptr)); +#endif #define ZALLOC(strm, items, size) \ (*((strm)->zalloc))((strm)->opaque, (items), (size)) #define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr)) #define TRY_FREE(s, p) {if (p) ZFREE(s, p);} +/* Reverse the bytes in a 32-bit value */ +#define ZSWAP32(q) ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \ + (((q) & 0xff00) << 8) + (((q) & 0xff) << 24)) + #endif /* ZUTIL_H */ diff --git a/dlg/dlg_about.cpp b/dlg/dlg_about.cpp deleted file mode 100644 index c239dae76..000000000 --- a/dlg/dlg_about.cpp +++ /dev/null @@ -1,178 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" -#include - -#include "dlg_about.h" - -#include "../common.h" -#include "../gfx.h" -#include "../resource.h" -#include "../stats.h" -#include "../string.h" -#include "../taiga.h" -#include "../time.h" - -#include "../win32/win_gdi.h" - -class AboutDialog AboutDialog; - -// ============================================================================= - -#define NOTE_COUNT 32 - -int note_index; -int note_list[NOTE_COUNT][2] = { - {84, 1}, // 1/2 - {84, 2}, // 1/4 - {86, 4}, // 1/8 - {84, 2}, // 1/4 - {82, 2}, // 1/4 - {81, 2}, // 1/4 - {77, 4}, // 1/8 - {79, 4}, // 1/8 - {72, 4}, // 1/8 - {77, 1}, // 1/2 - {76, 4}, // 1/8 - {77, 4}, // 1/8 - {79, 4}, // 1/8 - {81, 2}, // 1/4 - {79, 2}, // 1/4 - {77, 2}, // 1/4 - {79, 2}, // 1/4 - {81, 4}, // 1/8 - {84, 1}, // 1/2 - {84, 2}, // 1/2 - {86, 4}, // 1/8 - {84, 2}, // 1/4 - {82, 2}, // 1/4 - {81, 2}, // 1/4 - {77, 4}, // 1/8 - {79, 4}, // 1/8 - {72, 4}, // 1/8 - {77, 1}, // 1/2 - {76, 4}, // 1/8 - {77, 4}, // 1/8 - {76, 4}, // 1/8 - {74, 1} // 1/1 -}; - -float NoteToFrequency(int n) { - if (n < 0 || n > 119) return -1.0f; - return static_cast(440.0 * pow(2.0, static_cast(n - 57) / 12.0)); -} - -// ============================================================================= - -AboutDialog::AboutDialog() { - RegisterDlgClass(L"TaigaAboutW"); -} - -BOOL AboutDialog::OnDestroy() { - KillTimer(GetWindowHandle(), TIMER_TAIGA); - return TRUE; -} - -BOOL AboutDialog::OnInitDialog() { - rich_edit_.Attach(GetDlgItem(IDC_RICHEDIT_ABOUT)); - rich_edit_.SendMessage(EM_AUTOURLDETECT, TRUE /* AURL_ENABLEURL */); - rich_edit_.SetEventMask(ENM_LINK); - - wstring text = - L"{\\rtf1\\ansi\\deff0" - L"{\\fonttbl" - L"{\\f0 Segoe UI;}" - L"}" - L"\\deflang1024\\fs18" - L"\\b " APP_NAME L"\\b0\\line " - L"version " APP_VERSION L"\\line " - L"built on " APP_BUILD L"\\line\\line " - L"\\b Author:\\b0\\line " - L"Eren 'erengy' Okka\\line\\line " - L"\\b Committers and other contributors:\\b0\\line " - L"saka, Diablofan, slevir, LordGravewish\\line\\line " - L"\\b Third party stuff that is used by Taiga:\\b0\\line " - L"- Fugue Icons 3.4.5, Copyright (c) 2012, Yusuke Kamiyamane\\line " - L"- OAuth class is based on codebrook-twitter-oauth example code, Copyright (c) 2010, Brook Miles\\line " - L"- pugixml parser version 1.2, Copyright (c) 2006-2012, Arseny Kapoulkine\\line " - L"- zlib version 1.2.5, Copyright (c) 1995-2010, Jean-loup Gailly and Mark Adler\\line\\line " - L"\\b Links:\\b0\\line " - L"- Home page {\\field{\\*\\fldinst HYPERLINK \"http://taiga.erengy.com\"}{\\fldrslt http://taiga.erengy.com}}\\line " - L"- Google Code {\\field{\\*\\fldinst HYPERLINK \"http://code.google.com/p/taiga/\"}{\\fldrslt http://code.google.com/p/taiga/}}\\line " - L"- MyAnimeList club {\\field{\\*\\fldinst HYPERLINK \"http://myanimelist.net/clubs.php?cid=21400\"}{\\fldrslt http://myanimelist.net/clubs.php?cid=21400}}\\line " - L"- IRC channel {\\field{\\*\\fldinst HYPERLINK \"irc://irc.rizon.net/taiga\"}{\\fldrslt irc://irc.rizon.net/taiga}}" - L"}"; - rich_edit_.SetTextEx(ToANSI(text)); - - return TRUE; -} - -BOOL AboutDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_COMMAND: { - // Icon click - if (HIWORD(wParam) == STN_DBLCLK) { - Stats.tigers_harmed++; - SetTimer(hwnd, TIMER_TAIGA, 100, nullptr); - note_index = 0; - return TRUE; - } - break; - } - - case WM_NOTIFY: { - switch (reinterpret_cast(lParam)->code) { - // Execute link - case EN_LINK: { - auto en_link = reinterpret_cast(lParam); - if (en_link->msg == WM_LBUTTONUP) { - ExecuteLink(rich_edit_.GetTextRange(&en_link->chrg)); - return TRUE; - } - break; - } - } - break; - } - } - - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - -void AboutDialog::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { - win32::Dc dc = hdc; - win32::Rect rect; - - // Paint background - GetClientRect(&rect); - rect.left = ScaleX(static_cast(48 * 1.5f)); - dc.FillRect(rect, ::GetSysColor(COLOR_WINDOW)); -} - -void AboutDialog::OnTimer(UINT_PTR nIDEvent) { - if (note_index == NOTE_COUNT) { - KillTimer(GetWindowHandle(), TIMER_TAIGA); - SetText(L"About"); - note_index = 0; - } else { - if (note_index == 0) SetText(L"Orange"); - Beep((DWORD)NoteToFrequency(note_list[note_index][0]), 800 / note_list[note_index][1]); - note_index++; - } -} \ No newline at end of file diff --git a/dlg/dlg_anime_info_page.cpp b/dlg/dlg_anime_info_page.cpp deleted file mode 100644 index d79177b02..000000000 --- a/dlg/dlg_anime_info_page.cpp +++ /dev/null @@ -1,468 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_anime_info.h" -#include "dlg_anime_info_page.h" - -#include "dlg_input.h" - -#include "../anime.h" -#include "../anime_db.h" -#include "../common.h" -#include "../history.h" -#include "../http.h" -#include "../myanimelist.h" -#include "../recognition.h" -#include "../resource.h" -#include "../settings.h" -#include "../string.h" -#include "../theme.h" - -// ============================================================================= - -PageBaseInfo::PageBaseInfo() - : anime_id_(anime::ID_UNKNOWN) { -} - -BOOL PageBaseInfo::OnInitDialog() { - // Set new font for headers - for (int i = 0; i < 3; i++) { - SendDlgItemMessage(IDC_STATIC_HEADER1 + i, WM_SETFONT, - reinterpret_cast(UI.font_bold.Get()), FALSE); - } - - return TRUE; -} - -INT_PTR PageBaseInfo::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_CTLCOLORSTATIC: { - if (!parent->IsTabVisible()) { - win32::Dc dc = reinterpret_cast(wParam); - HWND hwnd_control = reinterpret_cast(lParam); - dc.SetBkMode(TRANSPARENT); - dc.DetachDC(); - if (hwnd_control == GetDlgItem(IDC_EDIT_ANIME_ALT)) - return reinterpret_cast(UI.brush_background.Get()); - return reinterpret_cast(::GetSysColorBrush(COLOR_WINDOW)); - } - break; - } - } - - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - -void PageBaseInfo::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { - win32::Dc dc = hdc; - win32::Rect rect; - - // Paint background - rect.Copy(lpps->rcPaint); - if (!parent->IsTabVisible()) - dc.FillRect(rect, ::GetSysColor(COLOR_WINDOW)); - - // Paint header lines - for (int i = 0; i < 3; i++) { - win32::Rect rect_header; - win32::Window header = GetDlgItem(IDC_STATIC_HEADER1 + i); - header.GetWindowRect(m_hWindow, &rect_header); - rect_header.top = rect_header.bottom + 3; - rect_header.bottom = rect_header.top + 1; - dc.FillRect(rect_header, ::GetSysColor(COLOR_ACTIVEBORDER)); - rect_header.Offset(0, 1); - dc.FillRect(rect_header, ::GetSysColor(COLOR_WINDOW)); - header.SetWindowHandle(nullptr); - } -} - -void PageBaseInfo::OnSize(UINT uMsg, UINT nType, SIZE size) { - switch (uMsg) { - case WM_SIZE: { - win32::Rect rect; - rect.Set(0, 0, size.cx, size.cy); - rect.Inflate(-ScaleX(WIN_CONTROL_MARGIN), -ScaleY(WIN_CONTROL_MARGIN)); - - // Headers - for (int i = 0; i < 3; i++) { - win32::Rect rect_header; - win32::Window header = GetDlgItem(IDC_STATIC_HEADER1 + i); - header.GetWindowRect(m_hWindow, &rect_header); - rect_header.right = rect.right; - header.SetPosition(nullptr, rect_header); - header.SetWindowHandle(nullptr); - } - - // Redraw - InvalidateRect(); - } - } -} - -// ============================================================================= - -void PageSeriesInfo::OnSize(UINT uMsg, UINT nType, SIZE size) { - PageBaseInfo::OnSize(uMsg, nType, size); - - switch (uMsg) { - case WM_SIZE: { - win32::Rect rect; - rect.Set(0, 0, size.cx, size.cy); - rect.Inflate(-ScaleX(WIN_CONTROL_MARGIN), -ScaleY(WIN_CONTROL_MARGIN)); - - // Synonyms - win32::Rect rect_child; - win32::Window window = GetDlgItem(IDC_EDIT_ANIME_ALT); - window.GetWindowRect(m_hWindow, &rect_child); - rect_child.right = rect.right - ScaleX(WIN_CONTROL_MARGIN); - window.SetPosition(nullptr, rect_child); - - // Details - window.SetWindowHandle(GetDlgItem(IDC_STATIC_ANIME_DETAILS)); - window.GetWindowRect(m_hWindow, &rect_child); - rect_child.right = rect.right - ScaleX(WIN_CONTROL_MARGIN); - window.SetPosition(nullptr, rect_child); - - // Synopsis - window.SetWindowHandle(GetDlgItem(IDC_EDIT_ANIME_SYNOPSIS)); - window.GetWindowRect(m_hWindow, &rect_child); - rect_child.right = rect.right - ScaleX(WIN_CONTROL_MARGIN); - rect_child.bottom = rect.bottom; - window.SetPosition(nullptr, rect_child); - window.SetWindowHandle(nullptr); - } - } -} - -void PageSeriesInfo::Refresh(int anime_id, bool connect) { - if (anime_id <= anime::ID_UNKNOWN) return; - - anime_id_ = anime_id; - auto anime_item = AnimeDatabase.FindItem(anime_id_); - - // Set synonyms - wstring text = Join(anime_item->GetSynonyms(), L", "); - if (text.empty()) text = L"-"; - SetDlgItemText(IDC_EDIT_ANIME_ALT, text.c_str()); - - // Set information - #define ADD_INFOLINE(x, y) (x.empty() ? y : x) - text = mal::TranslateType(anime_item->GetType()) + L"\n" + - mal::TranslateNumber(anime_item->GetEpisodeCount(), L"Unknown") + L"\n" + - mal::TranslateStatus(anime_item->GetAiringStatus()) + L"\n" + - mal::TranslateDateToSeason(anime_item->GetDate(anime::DATE_START)) + L"\n" + - ADD_INFOLINE(anime_item->GetGenres(), L"Unknown") + L"\n" + - ADD_INFOLINE(anime_item->GetProducers(), L"Unknown") + L"\n" + - ADD_INFOLINE(anime_item->GetScore(), L"0.00") + L"\n" + - ADD_INFOLINE(anime_item->GetPopularity(), L"#0"); - #undef ADD_INFOLINE - SetDlgItemText(IDC_STATIC_ANIME_DETAILS, text.c_str()); - - // Set synopsis - text = anime_item->GetSynopsis(); - SetDlgItemText(IDC_EDIT_ANIME_SYNOPSIS, text.c_str()); - if (connect) { - if (anime_item->IsOldEnough() || anime_item->GetSynopsis().empty()) { - mal::SearchAnime(anime_id_, anime_item->GetTitle()); - } else if (anime_item->GetGenres().empty() || anime_item->GetScore().empty()) { - mal::GetAnimeDetails(anime_id_); - } - } -} - -// ============================================================================= - -BOOL PageMyInfo::OnCommand(WPARAM wParam, LPARAM lParam) { - auto anime_item = AnimeDatabase.FindItem(anime_id_); - - switch (LOWORD(wParam)) { - // Browse anime folder - case IDC_BUTTON_BROWSE: { - wstring default_path, path; - if (!anime_item->GetFolder().empty()) { - default_path = anime_item->GetFolder(); - } else if (!Settings.Folders.root.empty()) { - default_path = Settings.Folders.root.front(); - } - if (BrowseForFolder(m_hWindow, L"Choose an anime folder", default_path, path)) { - SetDlgItemText(IDC_EDIT_ANIME_FOLDER, path.c_str()); - } - return TRUE; - } - - // User changed rewatching checkbox - case IDC_CHECK_ANIME_REWATCH: - if (HIWORD(wParam) == BN_CLICKED) { - win32::ComboBox m_Combo = GetDlgItem(IDC_COMBO_ANIME_STATUS); - win32::Spin m_Spin = GetDlgItem(IDC_SPIN_PROGRESS); - int episode_value; m_Spin.GetPos32(episode_value); - if (IsDlgButtonChecked(IDC_CHECK_ANIME_REWATCH)) { - if (anime_item->GetMyStatus() == mal::MYSTATUS_COMPLETED && episode_value == anime_item->GetEpisodeCount()) { - m_Spin.SetPos32(0); - } - m_Combo.Enable(FALSE); - m_Combo.SetCurSel(mal::MYSTATUS_COMPLETED - 1); - } else { - if (episode_value == 0) { - m_Spin.SetPos32(anime_item->GetMyLastWatchedEpisode()); - } - m_Combo.Enable(); - m_Combo.SetCurSel(anime_item->GetMyStatus() - 1); - } - m_Spin.SetWindowHandle(nullptr); - m_Combo.SetWindowHandle(nullptr); - return TRUE; - } - break; - - // User changed status dropdown - case IDC_COMBO_ANIME_STATUS: - if (HIWORD(wParam) == CBN_SELENDOK) { - // Selected "Completed" - win32::ComboBox m_Combo = GetDlgItem(IDC_COMBO_ANIME_STATUS); - if (m_Combo.GetItemData(m_Combo.GetCurSel()) == mal::MYSTATUS_COMPLETED) { - if (anime_item->GetMyStatus() != mal::MYSTATUS_COMPLETED && anime_item->GetEpisodeCount() > 0) { - SendDlgItemMessage(IDC_SPIN_PROGRESS, UDM_SETPOS32, 0, anime_item->GetEpisodeCount()); - } - } - m_Combo.SetWindowHandle(nullptr); - return TRUE; - } - break; - } - - return FALSE; -} - -LRESULT PageMyInfo::OnNotify(int idCtrl, LPNMHDR pnmh) { - switch (pnmh->idFrom) { - case IDC_LINK_ANIME_FANSUB: - switch (pnmh->code) { - case NM_CLICK: { - // Set/change fansub group preference - vector groups; - anime::GetFansubFilter(anime_id_, groups); - wstring text = Join(groups, L", "); - InputDialog dlg; - dlg.title = AnimeDatabase.FindItem(anime_id_)->GetTitle(); - dlg.info = L"Please enter your fansub group preference for this title:"; - dlg.text = text; - dlg.Show(AnimeDialog.GetWindowHandle()); - if (dlg.result == IDOK) - if (anime::SetFansubFilter(anime_id_, dlg.text)) - RefreshFansubPreference(); - return TRUE; - } - } - } - - return 0; -} - -void PageMyInfo::Refresh(int anime_id) { - if (anime_id <= anime::ID_UNKNOWN) return; - - anime_id_ = anime_id; - auto anime_item = AnimeDatabase.FindItem(anime_id_); - - if (!anime_item->IsInList()) return; - - // Episodes watched - SendDlgItemMessage(IDC_SPIN_PROGRESS, UDM_SETRANGE32, 0, - anime_item->GetEpisodeCount() > 0 ? anime_item->GetEpisodeCount() : 9999); - SendDlgItemMessage(IDC_SPIN_PROGRESS, UDM_SETPOS32, 0, anime_item->GetMyLastWatchedEpisode()); - - // Re-watching - CheckDlgButton(IDC_CHECK_ANIME_REWATCH, anime_item->GetMyRewatching()); - EnableDlgItem(IDC_CHECK_ANIME_REWATCH, anime_item->GetMyStatus() == mal::MYSTATUS_COMPLETED); - - // Status - win32::ComboBox m_Combo = GetDlgItem(IDC_COMBO_ANIME_STATUS); - if (m_Combo.GetCount() == 0) { - for (int i = mal::MYSTATUS_WATCHING; i <= mal::MYSTATUS_PLANTOWATCH; i++) { - if (i != mal::MYSTATUS_UNKNOWN) { - m_Combo.AddItem(mal::TranslateMyStatus(i, false).c_str(), i); - } - } - } - int status = anime_item->GetMyStatus(); - if (status == mal::MYSTATUS_PLANTOWATCH) status--; - m_Combo.SetCurSel(status - 1); - m_Combo.Enable(!anime_item->GetMyRewatching()); - m_Combo.SetWindowHandle(nullptr); - - // Score - m_Combo.SetWindowHandle(GetDlgItem(IDC_COMBO_ANIME_SCORE)); - if (m_Combo.GetCount() == 0) { - m_Combo.AddString(L"(10) Masterpiece"); - m_Combo.AddString(L"(9) Great"); - m_Combo.AddString(L"(8) Very Good"); - m_Combo.AddString(L"(7) Good"); - m_Combo.AddString(L"(6) Fine"); - m_Combo.AddString(L"(5) Average"); - m_Combo.AddString(L"(4) Bad"); - m_Combo.AddString(L"(3) Very Bad"); - m_Combo.AddString(L"(2) Horrible"); - m_Combo.AddString(L"(1) Unwatchable"); - m_Combo.AddString(L"(0) No Score"); - } - m_Combo.SetCurSel(10 - anime_item->GetMyScore()); - m_Combo.SetWindowHandle(nullptr); - - // Tags - win32::Edit m_Edit = GetDlgItem(IDC_EDIT_ANIME_TAGS); - m_Edit.SetCueBannerText(L"Enter tags here, separated by a comma (e.g. tag1, tag2)"); - m_Edit.SetText(anime_item->GetMyTags()); - m_Edit.SetWindowHandle(nullptr); - - // Date limits and defaults - if (mal::IsValidDate(anime_item->GetDate(anime::DATE_START))) { - SYSTEMTIME stSeriesStart = anime_item->GetDate(anime::DATE_START); - SendDlgItemMessage(IDC_DATETIME_START, DTM_SETRANGE, GDTR_MIN, (LPARAM)&stSeriesStart); - SendDlgItemMessage(IDC_DATETIME_START, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM)&stSeriesStart); - SendDlgItemMessage(IDC_DATETIME_FINISH, DTM_SETRANGE, GDTR_MIN, (LPARAM)&stSeriesStart); - SendDlgItemMessage(IDC_DATETIME_FINISH, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM)&stSeriesStart); - } - if (mal::IsValidDate(anime_item->GetDate(anime::DATE_END))) { - SYSTEMTIME stSeriesEnd = anime_item->GetDate(anime::DATE_END); - SendDlgItemMessage(IDC_DATETIME_FINISH, DTM_SETRANGE, GDTR_MIN, (LPARAM)&stSeriesEnd); - SendDlgItemMessage(IDC_DATETIME_FINISH, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM)&stSeriesEnd); - } - // Start date - if (mal::IsValidDate(anime_item->GetMyDate(anime::DATE_START))) { - SYSTEMTIME stMyStart = anime_item->GetMyDate(anime::DATE_START); - SendDlgItemMessage(IDC_DATETIME_START, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM)&stMyStart); - } else { - SendDlgItemMessage(IDC_DATETIME_START, DTM_SETSYSTEMTIME, GDT_NONE, 0); - } - // Finish date - if (mal::IsValidDate(anime_item->GetMyDate(anime::DATE_END))) { - SYSTEMTIME stMyFinish = anime_item->GetMyDate(anime::DATE_END); - SendDlgItemMessage(IDC_DATETIME_FINISH, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM)&stMyFinish); - } else { - SendDlgItemMessage(IDC_DATETIME_FINISH, DTM_SETSYSTEMTIME, GDT_NONE, 0); - } - - // Alternative titles - m_Edit.SetWindowHandle(GetDlgItem(IDC_EDIT_ANIME_ALT)); - m_Edit.SetCueBannerText(L"Enter alternative titles here, separated by a semicolon (e.g. Title 1; Title 2)"); - m_Edit.SetText(Join(anime_item->GetUserSynonyms(), L"; ")); - m_Edit.SetWindowHandle(nullptr); - CheckDlgButton(IDC_CHECK_ANIME_ALT, anime_item->GetUseAlternative()); - - // Folder - m_Edit.SetWindowHandle(GetDlgItem(IDC_EDIT_ANIME_FOLDER)); - m_Edit.SetText(anime_item->GetFolder()); - m_Edit.SetWindowHandle(nullptr); - - // Fansub group - RefreshFansubPreference(); -} - -void PageMyInfo::RefreshFansubPreference() { - if (anime_id_ <= anime::ID_UNKNOWN) return; - - wstring text; - vector groups; - - if (anime::GetFansubFilter(anime_id_, groups)) { - for (auto it = groups.begin(); it != groups.end(); ++it) { - if (!text.empty()) text += L" or "; - text += L"\"" + *it + L"\""; - } - } else { - text = L"None"; - } - - text = L"Fansub group preference: " + text + L" (Change)"; - SetDlgItemText(IDC_LINK_ANIME_FANSUB, text.c_str()); -} - -bool PageMyInfo::Save() { - auto anime_item = AnimeDatabase.FindItem(anime_id_); - - // Create item - EventItem event_item; - event_item.anime_id = anime_id_; - event_item.mode = HTTP_MAL_AnimeUpdate; - - // Episodes watched - event_item.episode = GetDlgItemInt(IDC_EDIT_ANIME_PROGRESS); - if (!mal::IsValidEpisode(*event_item.episode, -1, anime_item->GetEpisodeCount())) { - wstring msg = L"Please enter a valid episode number between 0-" + - ToWstr(anime_item->GetEpisodeCount()) + L"."; - MessageBox(msg.c_str(), L"Episodes watched", MB_OK | MB_ICONERROR); - return false; - } - - // Re-watching - event_item.enable_rewatching = IsDlgButtonChecked(IDC_CHECK_ANIME_REWATCH); - - // Score - event_item.score = 10 - GetComboSelection(IDC_COMBO_ANIME_SCORE); - - // Status - event_item.status = GetComboSelection(IDC_COMBO_ANIME_STATUS) + 1; - if (*event_item.status == mal::MYSTATUS_UNKNOWN) - event_item.status = *event_item.status + 1; - - // Tags - wstring tags; - GetDlgItemText(IDC_EDIT_ANIME_TAGS, tags); - event_item.tags = tags; - - // Start date - SYSTEMTIME stMyStart; - if (SendDlgItemMessage(IDC_DATETIME_START, DTM_GETSYSTEMTIME, 0, - reinterpret_cast(&stMyStart)) == GDT_NONE) { - event_item.date_start = mal::TranslateDateForApi(Date()); - } else { - event_item.date_start = mal::TranslateDateForApi( - Date(stMyStart.wYear, stMyStart.wMonth, stMyStart.wDay)); - } - // Finish date - SYSTEMTIME stMyFinish; - if (SendDlgItemMessage(IDC_DATETIME_FINISH, DTM_GETSYSTEMTIME, 0, - reinterpret_cast(&stMyFinish)) == GDT_NONE) { - event_item.date_finish = mal::TranslateDateForApi(Date()); - } else { - event_item.date_finish = mal::TranslateDateForApi( - Date(stMyFinish.wYear, stMyFinish.wMonth, stMyFinish.wDay)); - } - - // Alternative titles - wstring titles; - GetDlgItemText(IDC_EDIT_ANIME_ALT, titles); - anime_item->SetUserSynonyms(titles); - anime_item->SetUseAlternative(IsDlgButtonChecked(IDC_CHECK_ANIME_ALT) == TRUE); - Meow.UpdateCleanTitles(anime_id_); - - // Folder - wstring folder; - GetDlgItemText(IDC_EDIT_ANIME_FOLDER, folder); - anime_item->SetFolder(folder); - - // Save settings - Settings.Save(); - - // Add item to event queue - History.queue.Add(event_item); - return true; -} \ No newline at end of file diff --git a/dlg/dlg_feed_condition.cpp b/dlg/dlg_feed_condition.cpp deleted file mode 100644 index b07d469fc..000000000 --- a/dlg/dlg_feed_condition.cpp +++ /dev/null @@ -1,354 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_feed_condition.h" - -#include "../anime_db.h" -#include "../feed.h" -#include "../myanimelist.h" -#include "../resource.h" -#include "../string.h" - -#include "../win32/win_gdi.h" - -class FeedConditionDialog FeedConditionDialog; - -// ============================================================================= - -FeedConditionDialog::FeedConditionDialog() { - RegisterDlgClass(L"TaigaFeedConditionW"); -} - -FeedConditionDialog::~FeedConditionDialog() { -} - -// ============================================================================= - -BOOL FeedConditionDialog::OnInitDialog() { - // Set title - if (condition.element != 0 || condition.op != 0 || !condition.value.empty()) { - SetText(L"Edit Condition"); - } - - // Initialize - element_combo_.Attach(GetDlgItem(IDC_COMBO_FEED_ELEMENT)); - operator_combo_.Attach(GetDlgItem(IDC_COMBO_FEED_OPERATOR)); - value_combo_.Attach(GetDlgItem(IDC_COMBO_FEED_VALUE)); - - // Add elements - for (int i = 0; i < FEED_FILTER_ELEMENT_COUNT; i++) { - element_combo_.AddItem(Aggregator.filter_manager.TranslateElement(i).c_str(), i); - } - - // Set element - element_combo_.SetCurSel(condition.element); - ChooseElement(condition.element); - // Set operator - operator_combo_.SetCurSel(operator_combo_.FindItemData(condition.op)); - // Set value - switch (condition.element) { - case FEED_FILTER_ELEMENT_META_ID: { - value_combo_.SetCurSel(0); - for (int i = 0; i < value_combo_.GetCount(); i++) { - int anime_id = static_cast(value_combo_.GetItemData(i)); - if (anime_id == ToInt(condition.value)) { - value_combo_.SetCurSel(i); - break; - } - } - break; - } - case FEED_FILTER_ELEMENT_USER_STATUS: { - int value = ToInt(condition.value); - if (value == 6) value--; - value_combo_.SetCurSel(value); - break; - } - case FEED_FILTER_ELEMENT_META_STATUS: - case FEED_FILTER_ELEMENT_META_TYPE: - value_combo_.SetCurSel(ToInt(condition.value) - 1); - break; - case FEED_FILTER_ELEMENT_LOCAL_EPISODE_AVAILABLE: - value_combo_.SetCurSel(condition.value == L"True" ? 1 : 0); - break; - default: - value_combo_.SetText(condition.value); - } - - return TRUE; -} - -// ============================================================================= - -INT_PTR FeedConditionDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_CTLCOLORSTATIC: { - win32::Dc dc = reinterpret_cast(wParam); - dc.SetBkMode(TRANSPARENT); - dc.DetachDC(); - return reinterpret_cast(::GetSysColorBrush(COLOR_WINDOW)); - } - } - - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - -void FeedConditionDialog::OnCancel() { - // Clear data - condition.element = -1; - condition.op = -1; - condition.value = L""; - - // Exit - EndDialog(IDCANCEL); -} - -void FeedConditionDialog::OnOK() { - // Set values - condition.element = element_combo_.GetCurSel(); - condition.op = operator_combo_.GetItemData(operator_combo_.GetCurSel()); - switch (condition.element) { - case FEED_FILTER_ELEMENT_META_ID: - case FEED_FILTER_ELEMENT_META_STATUS: - case FEED_FILTER_ELEMENT_META_TYPE: - case FEED_FILTER_ELEMENT_USER_STATUS: - condition.value = ToWstr(value_combo_.GetItemData(value_combo_.GetCurSel())); - break; - default: - value_combo_.GetText(condition.value); - } - - // Exit - EndDialog(IDOK); -} - -BOOL FeedConditionDialog::OnCommand(WPARAM wParam, LPARAM lParam) { - switch (HIWORD(wParam)) { - case CBN_SELCHANGE: { - if (LOWORD(wParam) == IDC_COMBO_FEED_ELEMENT) { - ChooseElement(element_combo_.GetCurSel()); - } - break; - } - } - - return FALSE; -} - -void FeedConditionDialog::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { - win32::Dc dc = hdc; - win32::Rect rect; - - // Paint background - GetClientRect(&rect); - dc.FillRect(rect, ::GetSysColor(COLOR_WINDOW)); - - // Paint bottom area - win32::Rect rect_button; - ::GetClientRect(GetDlgItem(IDCANCEL), &rect_button); - rect.top = rect.bottom - (rect_button.Height() * 2); - dc.FillRect(rect, ::GetSysColor(COLOR_BTNFACE)); - - // Paint line - rect.bottom = rect.top + 1; - dc.FillRect(rect, ::GetSysColor(COLOR_ACTIVEBORDER)); -} - -// ============================================================================= - -void FeedConditionDialog::ChooseElement(int element_index) { - // Operator - LPARAM op_data = operator_combo_.GetItemData(operator_combo_.GetCurSel()); - operator_combo_.ResetContent(); - - #define ADD_OPERATOR(op) \ - operator_combo_.AddItem(Aggregator.filter_manager.TranslateOperator(op).c_str(), op); - - switch (element_index) { - case FEED_FILTER_ELEMENT_META_ID: - case FEED_FILTER_ELEMENT_EPISODE_NUMBER: - case FEED_FILTER_ELEMENT_META_DATE_START: - case FEED_FILTER_ELEMENT_META_DATE_END: - case FEED_FILTER_ELEMENT_META_EPISODES: - ADD_OPERATOR(FEED_FILTER_OPERATOR_EQUALS); - ADD_OPERATOR(FEED_FILTER_OPERATOR_NOTEQUALS); - ADD_OPERATOR(FEED_FILTER_OPERATOR_ISGREATERTHAN); - ADD_OPERATOR(FEED_FILTER_OPERATOR_ISGREATERTHANOREQUALTO); - ADD_OPERATOR(FEED_FILTER_OPERATOR_ISLESSTHAN); - ADD_OPERATOR(FEED_FILTER_OPERATOR_ISLESSTHANOREQUALTO); - break; - case FEED_FILTER_ELEMENT_LOCAL_EPISODE_AVAILABLE: - case FEED_FILTER_ELEMENT_META_STATUS: - case FEED_FILTER_ELEMENT_META_TYPE: - case FEED_FILTER_ELEMENT_USER_STATUS: - ADD_OPERATOR(FEED_FILTER_OPERATOR_EQUALS); - ADD_OPERATOR(FEED_FILTER_OPERATOR_NOTEQUALS); - break; - case FEED_FILTER_ELEMENT_EPISODE_TITLE: - case FEED_FILTER_ELEMENT_EPISODE_GROUP: - case FEED_FILTER_ELEMENT_EPISODE_VIDEO_TYPE: - case FEED_FILTER_ELEMENT_FILE_TITLE: - case FEED_FILTER_ELEMENT_FILE_CATEGORY: - case FEED_FILTER_ELEMENT_FILE_DESCRIPTION: - case FEED_FILTER_ELEMENT_FILE_LINK: - ADD_OPERATOR(FEED_FILTER_OPERATOR_EQUALS); - ADD_OPERATOR(FEED_FILTER_OPERATOR_NOTEQUALS); - ADD_OPERATOR(FEED_FILTER_OPERATOR_BEGINSWITH); - ADD_OPERATOR(FEED_FILTER_OPERATOR_ENDSWITH); - ADD_OPERATOR(FEED_FILTER_OPERATOR_CONTAINS); - ADD_OPERATOR(FEED_FILTER_OPERATOR_NOTCONTAINS); - break; - default: - for (int i = 0; i < FEED_FILTER_OPERATOR_COUNT; i++) { - ADD_OPERATOR(i); - } - } - - #undef ADD_OPERATOR - - int op_index = operator_combo_.FindItemData(op_data); - if (op_index == CB_ERR) - op_index = 0; - operator_combo_.SetCurSel(op_index); - - // =========================================================================== - - // Value - value_combo_.ResetContent(); - - RECT rect; - value_combo_.GetWindowRect(&rect); - int width = rect.right - rect.left; - int height = rect.bottom - rect.top; - ::ScreenToClient(m_hWindow, reinterpret_cast(&rect)); - - #define RECREATE_COMBO(style) \ - value_combo_.Create(0, WC_COMBOBOX, nullptr, \ - style | CBS_AUTOHSCROLL | WS_CHILD | WS_TABSTOP | WS_VISIBLE | WS_VSCROLL, \ - rect.left, rect.top, width, height * 2, \ - m_hWindow, nullptr, nullptr); - - switch (element_index) { - case FEED_FILTER_ELEMENT_FILE_CATEGORY: - RECREATE_COMBO(CBS_DROPDOWN); - value_combo_.AddString(L"Anime"); - value_combo_.AddString(L"Batch"); - value_combo_.AddString(L"Hentai"); - value_combo_.AddString(L"Non-English"); - value_combo_.AddString(L"Other"); - value_combo_.AddString(L"Raws"); - break; - case FEED_FILTER_ELEMENT_META_ID: - case FEED_FILTER_ELEMENT_EPISODE_TITLE: { - RECREATE_COMBO((element_index == FEED_FILTER_ELEMENT_META_ID ? CBS_DROPDOWNLIST : CBS_DROPDOWN)); - typedef std::pair anime_pair; - vector title_list; - for (auto it = AnimeDatabase.items.begin(); it != AnimeDatabase.items.end(); ++it) { - switch (it->second.GetMyStatus()) { - case mal::MYSTATUS_NOTINLIST: - case mal::MYSTATUS_COMPLETED: - case mal::MYSTATUS_DROPPED: - continue; - default: - title_list.push_back(std::make_pair(it->second.GetId(), - AnimeDatabase.FindItem(it->second.GetId())->GetTitle())); - } - } - std::sort(title_list.begin(), title_list.end(), - [](const anime_pair& a1, const anime_pair& a2) { - return CompareStrings(a1.second, a2.second) < 0; - }); - if (element_index == FEED_FILTER_ELEMENT_META_ID) { - value_combo_.AddString(L"(Unknown)"); - } - for (auto it = title_list.begin(); it != title_list.end(); ++it) { - value_combo_.AddItem(it->second.c_str(), it->first); - } - break; - } - case FEED_FILTER_ELEMENT_META_DATE_START: - case FEED_FILTER_ELEMENT_META_DATE_END: - RECREATE_COMBO(CBS_DROPDOWN); - value_combo_.AddString(static_cast(GetDate()).c_str()); - value_combo_.SetCueBannerText(L"YYYY-MM-DD"); - break; - case FEED_FILTER_ELEMENT_META_STATUS: - RECREATE_COMBO(CBS_DROPDOWNLIST); - value_combo_.AddItem(mal::TranslateStatus(mal::STATUS_AIRING).c_str(), mal::STATUS_AIRING); - value_combo_.AddItem(mal::TranslateStatus(mal::STATUS_FINISHED).c_str(), mal::STATUS_FINISHED); - value_combo_.AddItem(mal::TranslateStatus(mal::STATUS_NOTYETAIRED).c_str(), mal::STATUS_NOTYETAIRED); - break; - case FEED_FILTER_ELEMENT_META_TYPE: - RECREATE_COMBO(CBS_DROPDOWNLIST); - value_combo_.AddItem(mal::TranslateType(mal::TYPE_TV).c_str(), mal::TYPE_TV); - value_combo_.AddItem(mal::TranslateType(mal::TYPE_OVA).c_str(), mal::TYPE_OVA); - value_combo_.AddItem(mal::TranslateType(mal::TYPE_MOVIE).c_str(), mal::TYPE_MOVIE); - value_combo_.AddItem(mal::TranslateType(mal::TYPE_SPECIAL).c_str(), mal::TYPE_SPECIAL); - value_combo_.AddItem(mal::TranslateType(mal::TYPE_ONA).c_str(), mal::TYPE_ONA); - value_combo_.AddItem(mal::TranslateType(mal::TYPE_MUSIC).c_str(), mal::TYPE_MUSIC); - break; - case FEED_FILTER_ELEMENT_USER_STATUS: - RECREATE_COMBO(CBS_DROPDOWNLIST); - value_combo_.AddItem(mal::TranslateMyStatus(mal::MYSTATUS_NOTINLIST, false).c_str(), mal::MYSTATUS_NOTINLIST); - value_combo_.AddItem(mal::TranslateMyStatus(mal::MYSTATUS_WATCHING, false).c_str(), mal::MYSTATUS_WATCHING); - value_combo_.AddItem(mal::TranslateMyStatus(mal::MYSTATUS_COMPLETED, false).c_str(), mal::MYSTATUS_COMPLETED); - value_combo_.AddItem(mal::TranslateMyStatus(mal::MYSTATUS_ONHOLD, false).c_str(), mal::MYSTATUS_ONHOLD); - value_combo_.AddItem(mal::TranslateMyStatus(mal::MYSTATUS_DROPPED, false).c_str(), mal::MYSTATUS_DROPPED); - value_combo_.AddItem(mal::TranslateMyStatus(mal::MYSTATUS_PLANTOWATCH, false).c_str(), mal::MYSTATUS_PLANTOWATCH); - break; - case FEED_FILTER_ELEMENT_EPISODE_NUMBER: - case FEED_FILTER_ELEMENT_META_EPISODES: - RECREATE_COMBO(CBS_DROPDOWN); - value_combo_.AddString(L"%watched%"); - value_combo_.AddString(L"%total%"); - break; - case FEED_FILTER_ELEMENT_EPISODE_VERSION: - RECREATE_COMBO(CBS_DROPDOWN); - value_combo_.AddString(L"2"); - value_combo_.AddString(L"3"); - value_combo_.AddString(L"4"); - value_combo_.AddString(L"0"); - break; - case FEED_FILTER_ELEMENT_LOCAL_EPISODE_AVAILABLE: - RECREATE_COMBO(CBS_DROPDOWNLIST); - value_combo_.AddString(L"False"); - value_combo_.AddString(L"True"); - break; - case FEED_FILTER_ELEMENT_EPISODE_VIDEO_RESOLUTION: - RECREATE_COMBO(CBS_DROPDOWN); - value_combo_.AddString(L"1080p"); - value_combo_.AddString(L"720p"); - value_combo_.AddString(L"480p"); - value_combo_.AddString(L"400p"); - break; - case FEED_FILTER_ELEMENT_EPISODE_VIDEO_TYPE: - RECREATE_COMBO(CBS_DROPDOWN); - value_combo_.AddString(L"h264"); - value_combo_.AddString(L"x264"); - value_combo_.AddString(L"XviD"); - break; - default: - RECREATE_COMBO(CBS_DROPDOWN); - break; - } - - #undef RECREATE_COMBO - value_combo_.SetCurSel(0); -} \ No newline at end of file diff --git a/dlg/dlg_main.cpp b/dlg/dlg_main.cpp deleted file mode 100644 index 2cbc090ce..000000000 --- a/dlg/dlg_main.cpp +++ /dev/null @@ -1,1009 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_main.h" - -#include "dlg_about.h" -#include "dlg_anime_info.h" -#include "dlg_anime_list.h" -#include "dlg_history.h" -#include "dlg_search.h" -#include "dlg_season.h" -#include "dlg_settings.h" -#include "dlg_stats.h" -#include "dlg_test_recognition.h" -#include "dlg_torrent.h" -#include "dlg_update.h" - -#include "../anime_db.h" -#include "../anime_filter.h" -#include "../announce.h" -#include "../common.h" -#include "../debug.h" -#include "../gfx.h" -#include "../history.h" -#include "../http.h" -#include "../media.h" -#include "../monitor.h" -#include "../myanimelist.h" -#include "../process.h" -#include "../recognition.h" -#include "../resource.h" -#include "../settings.h" -#include "../stats.h" -#include "../string.h" -#include "../taiga.h" -#include "../theme.h" - -#include "../win32/win_gdi.h" -#include "../win32/win_taskbar.h" -#include "../win32/win_taskdialog.h" - -class MainDialog MainDialog; - -// ============================================================================= - -MainDialog::MainDialog() { - navigation.parent = this; - search_bar.parent = this; - - RegisterDlgClass(L"TaigaMainW"); -} - -BOOL MainDialog::OnInitDialog() { - // Set global variables - g_hMain = GetWindowHandle(); - - // Initialize window properties - InitWindowPosition(); - SetIconLarge(IDI_MAIN); - SetIconSmall(IDI_MAIN); - - // Create default brushes and fonts - UI.CreateBrushes(); - UI.CreateFonts(GetDC()); - - // Create controls - CreateDialogControls(); - - // Select default content page - navigation.SetCurrentPage(SIDEBAR_ITEM_ANIMELIST); - - // Start process timer - SetTimer(g_hMain, TIMER_MAIN, 1000, nullptr); - - // Add icon to taskbar - Taskbar.Create(g_hMain, nullptr, APP_TITLE); - - ChangeStatus(); - UpdateTip(); - UpdateTitle(); - - // Refresh menus - UpdateAllMenus(); - - // Apply start-up settings - if (Settings.Account.MAL.auto_sync) { - ExecuteAction(L"Synchronize"); - } - if (Settings.Program.StartUp.check_new_episodes) { - ScanAvailableEpisodes(anime::ID_UNKNOWN, false, true); - } - if (!Settings.Program.StartUp.minimize) { - Show(Settings.Program.Exit.remember_pos_size && Settings.Program.Position.maximized ? - SW_MAXIMIZE : SW_SHOWNORMAL); - } - if (Settings.Account.MAL.user.empty()) { - win32::TaskDialog dlg(APP_TITLE, TD_ICON_INFORMATION); - dlg.SetMainInstruction(L"Welcome to Taiga!"); - dlg.SetContent(L"Username is not set. Would you like to open settings window to set it now?"); - dlg.AddButton(L"Yes", IDYES); - dlg.AddButton(L"No", IDNO); - dlg.Show(g_hMain); - if (dlg.GetSelectedButtonID() == IDYES) - ExecuteAction(L"Settings", SECTION_SERVICES, PAGE_SERVICES_MAL); - } - if (Settings.Folders.watch_enabled) { - FolderMonitor.SetWindowHandle(GetWindowHandle()); - FolderMonitor.Enable(); - } - - // Success - return TRUE; -} - -void MainDialog::CreateDialogControls() { - // Create rebar - rebar.Attach(GetDlgItem(IDC_REBAR_MAIN)); - // Create menu toolbar - toolbar_menu.Attach(GetDlgItem(IDC_TOOLBAR_MENU)); - toolbar_menu.SetImageList(nullptr, 0, 0); - // Create main toolbar - toolbar_main.Attach(GetDlgItem(IDC_TOOLBAR_MAIN)); - toolbar_main.SetImageList(UI.ImgList24.GetHandle(), 24, 24); - toolbar_main.SendMessage(TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS | TBSTYLE_EX_MIXEDBUTTONS); - // Create search toolbar - toolbar_search.Attach(GetDlgItem(IDC_TOOLBAR_SEARCH)); - toolbar_search.SetImageList(UI.ImgList24.GetHandle(), 24, 24); - toolbar_search.SendMessage(TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS | TBSTYLE_EX_MIXEDBUTTONS); - // Create search text - edit.Attach(GetDlgItem(IDC_EDIT_SEARCH)); - edit.SetCueBannerText(L"Search list"); - edit.SetMargins(1, 16); - edit.SetParent(toolbar_search.GetWindowHandle()); - win32::Rect rcEdit; edit.GetRect(&rcEdit); - win32::Rect rcEditWindow; edit.GetWindowRect(&rcEditWindow); - win32::Rect rcToolbar; toolbar_search.GetClientRect(&rcToolbar); - int edit_y = (30 /*rcToolbar.Height()*/ - rcEditWindow.Height()) / 2; - edit.SetPosition(nullptr, 0, edit_y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); - // Create cancel search button - cancel_button.Attach(GetDlgItem(IDC_BUTTON_CANCELSEARCH)); - cancel_button.SetParent(edit.GetWindowHandle()); - cancel_button.SetPosition(nullptr, rcEdit.right + 1, 0, 16, 16); - // Create treeview control - treeview.Attach(GetDlgItem(IDC_TREE_MAIN)); - treeview.SendMessage(TVM_SETBKCOLOR, 0, ::GetSysColor(COLOR_3DFACE)); - treeview.SetImageList(UI.ImgList16.GetHandle()); - treeview.SetItemHeight(20); - treeview.SetTheme(); - if (Settings.Program.General.hide_sidebar) { - treeview.Hide(); - } - // Create status bar - statusbar.Attach(GetDlgItem(IDC_STATUSBAR_MAIN)); - statusbar.SetImageList(UI.ImgList16.GetHandle()); - statusbar.InsertPart(-1, 0, 0, 900, nullptr, nullptr); - statusbar.InsertPart(ICON16_CLOCK, 0, 0, 32, nullptr, nullptr); - - // Insert treeview items - treeview.hti.push_back(treeview.InsertItem(L"Now Playing", ICON16_PLAY, SIDEBAR_ITEM_NOWPLAYING, nullptr)); - treeview.hti.push_back(treeview.InsertItem(nullptr, -1, -1, nullptr)); - treeview.hti.push_back(treeview.InsertItem(L"Anime List", ICON16_DOCUMENT_A, SIDEBAR_ITEM_ANIMELIST, nullptr)); - treeview.hti.push_back(treeview.InsertItem(L"History", ICON16_CLOCK, SIDEBAR_ITEM_HISTORY, nullptr)); - treeview.hti.push_back(treeview.InsertItem(L"Statistics", ICON16_CHART, SIDEBAR_ITEM_STATS, nullptr)); - treeview.hti.push_back(treeview.InsertItem(nullptr, -1, -1, nullptr)); - treeview.hti.push_back(treeview.InsertItem(L"Search", ICON16_SEARCH, SIDEBAR_ITEM_SEARCH, nullptr)); - treeview.hti.push_back(treeview.InsertItem(L"Seasons", ICON16_CALENDAR, SIDEBAR_ITEM_SEASONS, nullptr)); - treeview.hti.push_back(treeview.InsertItem(L"Torrents", ICON16_FEED, SIDEBAR_ITEM_FEEDS, nullptr)); - if (History.queue.GetItemCount() > 0) { - treeview.RefreshHistoryCounter(); - } - - // Insert menu toolbar buttons - BYTE fsState = TBSTATE_ENABLED; - BYTE fsStyle0 = BTNS_AUTOSIZE | BTNS_DROPDOWN | BTNS_SHOWTEXT; - toolbar_menu.InsertButton(0, I_IMAGENONE, 100, fsState, fsStyle0, 0, L" File", nullptr); - toolbar_menu.InsertButton(1, I_IMAGENONE, 101, fsState, fsStyle0, 0, L" MyAnimeList", nullptr); - toolbar_menu.InsertButton(2, I_IMAGENONE, 102, fsState, fsStyle0, 0, L" Tools", nullptr); - toolbar_menu.InsertButton(3, I_IMAGENONE, 103, fsState, fsStyle0, 0, L" View", nullptr); - toolbar_menu.InsertButton(4, I_IMAGENONE, 104, fsState, fsStyle0, 0, L" Help", nullptr); - // Insert main toolbar buttons - BYTE fsStyle1 = BTNS_AUTOSIZE; - BYTE fsStyle2 = BTNS_AUTOSIZE | BTNS_WHOLEDROPDOWN; - toolbar_main.InsertButton(0, ICON24_SYNC, TOOLBAR_BUTTON_SYNCHRONIZE, - fsState, fsStyle1, 0, nullptr, L"Synchronize list"); - toolbar_main.InsertButton(1, ICON24_MAL, TOOLBAR_BUTTON_MAL, - fsState, fsStyle1, 1, nullptr, L"Go to my panel at MyAnimeList"); - toolbar_main.InsertButton(2, 0, 0, 0, BTNS_SEP, 0, nullptr, nullptr); - toolbar_main.InsertButton(3, ICON24_FOLDERS, TOOLBAR_BUTTON_FOLDERS, - fsState, fsStyle2, 3, nullptr, L"Root folders"); - toolbar_main.InsertButton(4, ICON24_TOOLS, TOOLBAR_BUTTON_TOOLS, - fsState, fsStyle2, 4, nullptr, L"External links"); - toolbar_main.InsertButton(5, 0, 0, 0, BTNS_SEP, 0, nullptr, nullptr); - toolbar_main.InsertButton(6, ICON24_SETTINGS, TOOLBAR_BUTTON_SETTINGS, - fsState, fsStyle1, 6, nullptr, L"Change program settings"); -#ifdef _DEBUG - toolbar_main.InsertButton(7, 0, 0, 0, BTNS_SEP, 0, nullptr, nullptr); - toolbar_main.InsertButton(8, ICON24_ABOUT, TOOLBAR_BUTTON_ABOUT, - fsState, fsStyle1, 8, nullptr, L"Debug"); -#endif - - // Insert rebar bands - UINT fMask = RBBIM_CHILD | RBBIM_CHILDSIZE | RBBIM_HEADERSIZE | RBBIM_SIZE | RBBIM_STYLE; - UINT fStyle = RBBS_NOGRIPPER; - rebar.InsertBand(toolbar_menu.GetWindowHandle(), - GetSystemMetrics(SM_CXSCREEN), - 0, 0, 0, 0, 0, 0, - HIWORD(toolbar_menu.GetButtonSize()), - fMask, fStyle); - rebar.InsertBand(toolbar_main.GetWindowHandle(), - GetSystemMetrics(SM_CXSCREEN), - WIN_CONTROL_MARGIN, 0, 0, 0, 0, 0, - HIWORD(toolbar_main.GetButtonSize()) + 2, - fMask, fStyle | RBBS_BREAK); - rebar.InsertBand(toolbar_search.GetWindowHandle(), - 0, WIN_CONTROL_MARGIN, 0, rcEditWindow.Width() + (WIN_CONTROL_MARGIN * 2), 0, 0, 0, - HIWORD(toolbar_search.GetButtonSize()), - fMask, fStyle); -} - -void MainDialog::InitWindowPosition() { - UINT flags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER; - const LONG min_w = ScaleX(710); - const LONG min_h = ScaleX(480); - - win32::Rect rcParent, rcWindow; - ::GetWindowRect(GetParent(), &rcParent); - rcWindow.Set( - Settings.Program.Position.x, - Settings.Program.Position.y, - Settings.Program.Position.x + Settings.Program.Position.w, - Settings.Program.Position.y + Settings.Program.Position.h); - - if (rcWindow.left < 0 || rcWindow.left >= rcParent.right || - rcWindow.top < 0 || rcWindow.top >= rcParent.bottom) { - flags |= SWP_NOMOVE; - } - if (rcWindow.Width() < min_w) { - rcWindow.right = rcWindow.left + min_w; - } - if (rcWindow.Height() < min_h) { - rcWindow.bottom = rcWindow.top + min_h; - } - if (rcWindow.Width() > rcParent.Width()) { - rcWindow.right = rcParent.left + rcParent.Width(); - } - if (rcWindow.Height() > rcParent.Height()) { - rcWindow.bottom = rcParent.top + rcParent.Height(); - } - if (rcWindow.Width() > 0 && rcWindow.Height() > 0 && - Settings.Program.Position.maximized == FALSE && - Settings.Program.Exit.remember_pos_size == TRUE) { - SetPosition(nullptr, rcWindow, flags); - if (flags & SWP_NOMOVE) { - CenterOwner(); - } - } - - SetSizeMin(min_w, min_h); - SetSnapGap(10); -} - -// ============================================================================= - -INT_PTR MainDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - // Log off / Shutdown - case WM_ENDSESSION: { - OnDestroy(); - return FALSE; - } - - // Monitor anime folders - case WM_MONITORCALLBACK: { - FolderMonitor.OnChange(reinterpret_cast(lParam)); - return TRUE; - } - - // Show menu - case WM_TAIGA_SHOWMENU: { - toolbar_wm.ShowMenu(); - return TRUE; - } - - // External programs - case WM_COPYDATA: { - auto pCDS = reinterpret_cast(lParam); - // JetAudio - if (pCDS->dwData == 0x3000 /* JRC_COPYDATA_ID_TRACK_FILENAME */) { - MediaPlayers.new_title = ToUTF8(reinterpret_cast(pCDS->lpData)); - return TRUE; - // Media Portal - } else if (pCDS->dwData == 0x1337) { - MediaPlayers.new_title = ToUTF8(reinterpret_cast(pCDS->lpData)); - return TRUE; - } - break; - } - } - - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - -BOOL MainDialog::PreTranslateMessage(MSG* pMsg) { - switch (pMsg->message) { - case WM_KEYDOWN: { - switch (pMsg->wParam) { - // Clear search text - case VK_ESCAPE: { - if (::GetFocus() == edit.GetWindowHandle()) { - edit.SetText(L""); - return TRUE; - } - break; - } - // Search - case VK_RETURN: { - if (::GetFocus() == edit.GetWindowHandle()) { - wstring text; - edit.GetText(text); - if (text.empty()) break; - switch (search_bar.mode) { - case SEARCH_MODE_MAL: { - ExecuteAction(L"SearchAnime(" + text + L")"); - return TRUE; - } - case SEARCH_MODE_FEED: { - TorrentDialog.Search(Settings.RSS.Torrent.search_url, text); - return TRUE; - } - } - } - break; - } - // Focus search box - case 'F': { - if (GetKeyState(VK_CONTROL) & 0x8000) { - edit.SetFocus(); - edit.SetSel(0, -1); - return TRUE; - } - break; - } - case VK_F3: { - edit.SetFocus(); - edit.SetSel(0, -1); - return TRUE; - } - case VK_F5: { - switch (navigation.GetCurrentPage()) { - case SIDEBAR_ITEM_ANIMELIST: - // Scan available episodes - ScanAvailableEpisodes(anime::ID_UNKNOWN, true, false); - return TRUE; - case SIDEBAR_ITEM_HISTORY: - // Refresh history - HistoryDialog.RefreshList(); - treeview.RefreshHistoryCounter(); - return TRUE; - case SIDEBAR_ITEM_STATS: - // Refresh stats - Stats.CalculateAll(); - StatsDialog.Refresh(); - return TRUE; - case SIDEBAR_ITEM_SEASONS: - // Refresh season data - SeasonDialog.RefreshData(); - return TRUE; - case SIDEBAR_ITEM_FEEDS: { - // Check new torrents - Feed* feed = Aggregator.Get(FEED_CATEGORY_LINK); - if (feed) { - edit.SetText(L""); - feed->Check(Settings.RSS.Torrent.source); - return TRUE; - } - break; - } - } - break; - } - } - break; - } - - // Forward mouse wheel messages to the active page - case WM_MOUSEWHEEL: { - // Ignoring the low-order word of wParam to avoid falling into an infinite - // message-forwarding loop - pMsg->wParam = MAKEWPARAM(0, HIWORD(pMsg->wParam)); - switch (navigation.GetCurrentPage()) { - case SIDEBAR_ITEM_ANIMELIST: - return AnimeListDialog.SendMessage( - pMsg->message, pMsg->wParam, pMsg->lParam); - case SIDEBAR_ITEM_HISTORY: - return HistoryDialog.SendMessage( - pMsg->message, pMsg->wParam, pMsg->lParam); - case SIDEBAR_ITEM_STATS: - return StatsDialog.SendMessage( - pMsg->message, pMsg->wParam, pMsg->lParam); - case SIDEBAR_ITEM_SEARCH: - return SearchDialog.SendMessage( - pMsg->message, pMsg->wParam, pMsg->lParam); - case SIDEBAR_ITEM_SEASONS: - return SeasonDialog.SendMessage( - pMsg->message, pMsg->wParam, pMsg->lParam); - case SIDEBAR_ITEM_FEEDS: - return TorrentDialog.SendMessage( - pMsg->message, pMsg->wParam, pMsg->lParam); - } - break; - } - - // Back & forward buttons are used for navigation - case WM_XBUTTONUP: { - switch (HIWORD(pMsg->wParam)) { - case XBUTTON1: - navigation.GoBack(); - break; - case XBUTTON2: - navigation.GoForward(); - break; - } - return TRUE; - } - } - - return FALSE; -} - -// ============================================================================= - -BOOL MainDialog::OnClose() { - if (Settings.Program.General.close) { - Hide(); - return TRUE; - } - - return FALSE; -} - -BOOL MainDialog::OnDestroy() { - if (Settings.Program.Exit.remember_pos_size) { - Settings.Program.Position.maximized = (GetWindowLong() & WS_MAXIMIZE) ? TRUE : FALSE; - if (Settings.Program.Position.maximized == FALSE) { - bool invisible = !IsVisible(); - if (invisible) ActivateWindow(GetWindowHandle()); - win32::Rect rcWindow; GetWindowRect(&rcWindow); - if (invisible) Hide(); - Settings.Program.Position.x = rcWindow.left; - Settings.Program.Position.y = rcWindow.top; - Settings.Program.Position.w = rcWindow.Width(); - Settings.Program.Position.h = rcWindow.Height(); - } - } - - AboutDialog.Destroy(); - AnimeDialog.Destroy(); - RecognitionTest.Destroy(); - SettingsDialog.Destroy(); - UpdateDialog.Destroy(); - - Taiga.Uninitialize(); - - return TRUE; -} - -void MainDialog::OnDropFiles(HDROP hDropInfo) { -#ifdef _DEBUG - WCHAR buffer[MAX_PATH]; - if (DragQueryFile(hDropInfo, 0, buffer, MAX_PATH) > 0) { - anime::Episode episode; - Meow.ExamineTitle(buffer, episode); - MessageBox(ReplaceVariables(Settings.Program.Notifications.format, episode).c_str(), APP_TITLE, MB_OK); - } -#endif -} - -LRESULT MainDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { - // Toolbar controls - if (idCtrl == IDC_TOOLBAR_MENU || - idCtrl == IDC_TOOLBAR_MAIN || - idCtrl == IDC_TOOLBAR_SEARCH) { - return OnToolbarNotify(reinterpret_cast(pnmh)); - - // Tree control - } else if (idCtrl == IDC_TREE_MAIN) { - return OnTreeNotify(reinterpret_cast(pnmh)); - - // Button control - } else if (idCtrl == IDC_BUTTON_CANCELSEARCH) { - if (pnmh->code == NM_CUSTOMDRAW) { - return cancel_button.OnCustomDraw(reinterpret_cast(pnmh)); - } - } - - return 0; -} - -void MainDialog::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { - // Paint sidebar - if (treeview.IsVisible()) { - win32::Dc dc = hdc; - win32::Rect rect; - - rect.Copy(rect_sidebar_); - dc.FillRect(rect, ::GetSysColor(COLOR_3DFACE)); - - rect.left = rect.right - 1; - dc.FillRect(rect, ::GetSysColor(COLOR_ACTIVEBORDER)); - } -} - -void MainDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { - switch (uMsg) { - case WM_ENTERSIZEMOVE: { - if (::IsAppThemed() && win32::GetWinVersion() >= win32::VERSION_VISTA) { - SetTransparency(200); - } - break; - } - case WM_EXITSIZEMOVE: { - if (::IsAppThemed() && win32::GetWinVersion() >= win32::VERSION_VISTA) { - SetTransparency(255); - } - break; - } - case WM_SIZE: { - if (IsIconic()) { - if (Settings.Program.General.minimize) Hide(); - return; - } - UpdateControlPositions(&size); - break; - } - } -} - -// ============================================================================= - -/* Timer */ - -// This function is very delicate, even order of things are important. -// Please be careful with what you change. - -void MainDialog::OnTimer(UINT_PTR nIDEvent) { - // Measure stability - Stats.uptime++; - // Refresh statistics window - if (StatsDialog.IsVisible()) { - if (Stats.uptime % 10 == 0) { // Recalculate every 10 seconds - Stats.CalculateAll(); - } - StatsDialog.Refresh(); - } - - // =========================================================================== - - // Free memory - Taiga.ticker_memory++; - if (Taiga.ticker_memory >= 10 * 60) { // 10 minutes - Taiga.ticker_memory = 0; - ImageDatabase.FreeMemory(); - } - - // =========================================================================== - - // Check event queue - Taiga.ticker_queue++; - if (Taiga.ticker_queue >= 5 * 60) { // 5 minutes - Taiga.ticker_queue = 0; - if (History.queue.updating == false) { - History.queue.Check(true); - } - } - - // =========================================================================== - - // Check new episodes (if folder monitor is disabled) - if (!Settings.Folders.watch_enabled) { - Taiga.ticker_new_episodes++; - if (Taiga.ticker_new_episodes >= 30 * 60) { // 30 minutes - Taiga.ticker_new_episodes = 0; - ScanAvailableEpisodes(anime::ID_UNKNOWN, false, true); - } - } - - // =========================================================================== - - // Check feeds - for (unsigned int i = 0; i < Aggregator.feeds.size(); i++) { - switch (Aggregator.feeds[i].category) { - case FEED_CATEGORY_LINK: - if (Settings.RSS.Torrent.check_enabled) { - Aggregator.feeds[i].ticker++; - } - if (Settings.RSS.Torrent.check_enabled && Settings.RSS.Torrent.check_interval) { - if (TorrentDialog.IsWindow()) { - TorrentDialog.SetTimerText(L"Check new torrents [" + - ToTimeString(Settings.RSS.Torrent.check_interval * 60 - Aggregator.feeds[i].ticker) + L"]"); - } - if (Aggregator.feeds[i].ticker >= Settings.RSS.Torrent.check_interval * 60) { - Aggregator.feeds[i].Check(Settings.RSS.Torrent.source, true); - } - } else { - if (TorrentDialog.IsWindow()) { - TorrentDialog.SetTimerText(L"Check new torrents"); - } - } - break; - } - } - - // =========================================================================== - - // Check process list for media players - auto anime_item = AnimeDatabase.FindItem(CurrentEpisode.anime_id); - int media_index = MediaPlayers.Check(); - - // Media player is running - if (media_index > -1) { - // Started to watch? - if (CurrentEpisode.anime_id == anime::ID_UNKNOWN) { - // Recognized? - if (Settings.Program.General.enable_recognition) { - if (Meow.ExamineTitle(MediaPlayers.current_title, CurrentEpisode)) { - anime_item = Meow.MatchDatabase(CurrentEpisode, false, true, true, true, true, true); - if (anime_item) { - MediaPlayers.SetTitleChanged(false); - CurrentEpisode.Set(anime_item->GetId()); - anime_item->StartWatching(CurrentEpisode); - return; - } - } - // Not recognized - CurrentEpisode.Set(anime::ID_NOTINLIST); - if (CurrentEpisode.title.empty()) { -#ifdef _DEBUG - ChangeStatus(MediaPlayers.items[MediaPlayers.index].name + L" is running."); -#endif - } else { - MediaPlayers.SetTitleChanged(false); - NowPlayingDialog.SetCurrentId(anime::ID_NOTINLIST); - ChangeStatus(L"Watching: " + CurrentEpisode.title + - PushString(L" #", CurrentEpisode.number) + L" (Not recognized)"); - if (Settings.Program.Notifications.notrecognized) { - wstring tip_text = ReplaceVariables(Settings.Program.Notifications.format, CurrentEpisode); - tip_text += L"\nClick here to view similar titles for this anime."; - Taiga.current_tip_type = TIPTYPE_NOWPLAYING; - Taskbar.Tip(L"", L"", 0); - Taskbar.Tip(tip_text.c_str(), L"Media is not in your list", NIIF_WARNING); - } - } - } - - // Already watching or not recognized before - } else { - // Tick and compare with delay time - if (Taiga.ticker_media > -1 && Taiga.ticker_media <= Settings.Account.Update.delay) { - if (Taiga.ticker_media == Settings.Account.Update.delay) { - // Disable ticker - Taiga.ticker_media = -1; - // Announce current episode - Announcer.Do(ANNOUNCE_TO_HTTP | ANNOUNCE_TO_MESSENGER | ANNOUNCE_TO_MIRC | ANNOUNCE_TO_SKYPE); - // Update - if (!Settings.Account.Update.wait_mp) - if (anime_item) - anime_item->UpdateList(CurrentEpisode); - return; - } - if (Settings.Account.Update.check_player == FALSE || - MediaPlayers.items[media_index].window_handle == GetForegroundWindow()) - Taiga.ticker_media++; - } - // Caption changed? - if (MediaPlayers.TitleChanged()) { - MediaPlayers.SetTitleChanged(false); - ChangeStatus(); - bool processed = CurrentEpisode.processed; // TODO: not a good solution... - CurrentEpisode.Set(anime::ID_UNKNOWN); - if (anime_item) { - anime_item->EndWatching(CurrentEpisode); - CurrentEpisode.anime_id = anime_item->GetId(); - CurrentEpisode.processed = processed; - anime_item->UpdateList(CurrentEpisode); - CurrentEpisode.anime_id = anime::ID_UNKNOWN; - } - Taiga.ticker_media = 0; - } - } - - // Media player is NOT running - } else { - // Was running, but not watching - if (!anime_item) { - if (MediaPlayers.index_old > 0){ - ChangeStatus(); - CurrentEpisode.Set(anime::ID_UNKNOWN); - MediaPlayers.index_old = 0; - NowPlayingDialog.SetCurrentId(anime::ID_UNKNOWN); - } - - // Was running and watching - } else { - bool processed = CurrentEpisode.processed; // TODO: temporary solution... - CurrentEpisode.Set(anime::ID_UNKNOWN); - anime_item->EndWatching(CurrentEpisode); - if (Settings.Account.Update.wait_mp) { - CurrentEpisode.anime_id = anime_item->GetId(); - CurrentEpisode.processed = processed; - anime_item->UpdateList(CurrentEpisode); - CurrentEpisode.anime_id = anime::ID_UNKNOWN; - } - Taiga.ticker_media = 0; - } - } - - // Update status timer - UpdateStatusTimer(); -} - -// ============================================================================= - -/* Taskbar */ - -void MainDialog::OnTaskbarCallback(UINT uMsg, LPARAM lParam) { - // Taskbar creation notification - if (uMsg == WM_TASKBARCREATED) { - Taskbar.Create(m_hWindow, nullptr, APP_TITLE); - - // Windows 7 taskbar interface - } else if (uMsg == WM_TASKBARBUTTONCREATED) { - TaskbarList.Initialize(m_hWindow); - - // Taskbar callback - } else if (uMsg == WM_TASKBARCALLBACK) { - switch (lParam) { - case NIN_BALLOONSHOW: { - debug::Print(L"Tip type: " + ToWstr(Taiga.current_tip_type) + L"\n"); - break; - } - case NIN_BALLOONTIMEOUT: { - Taiga.current_tip_type = TIPTYPE_DEFAULT; - break; - } - case NIN_BALLOONUSERCLICK: { - switch (Taiga.current_tip_type) { - case TIPTYPE_NOWPLAYING: - navigation.SetCurrentPage(SIDEBAR_ITEM_NOWPLAYING); - break; - case TIPTYPE_SEARCH: - ExecuteAction(L"SearchAnime(" + CurrentEpisode.title + L")"); - break; - case TIPTYPE_TORRENT: - navigation.SetCurrentPage(SIDEBAR_ITEM_FEEDS); - break; - case TIPTYPE_UPDATEFAILED: - History.queue.Check(false); - break; - } - ActivateWindow(GetWindowHandle()); - Taiga.current_tip_type = TIPTYPE_DEFAULT; - break; - } - case WM_LBUTTONUP: - case WM_LBUTTONDBLCLK: { - ActivateWindow(m_hWindow); - break; - } - case WM_RBUTTONUP: { - UpdateAllMenus(AnimeListDialog.GetCurrentItem()); - SetForegroundWindow(); - ExecuteAction(UI.Menus.Show(m_hWindow, 0, 0, L"Tray")); - UpdateAllMenus(AnimeListDialog.GetCurrentItem()); - break; - } - } - } -} - -// ============================================================================= - -void MainDialog::ChangeStatus(wstring str) { - if (!str.empty()) str = L" " + str; - statusbar.SetText(str.c_str()); -} - -void MainDialog::EnableInput(bool enable) { - // Toolbar buttons - toolbar_main.EnableButton(TOOLBAR_BUTTON_SYNCHRONIZE, enable); - // Content - AnimeListDialog.Enable(enable); - HistoryDialog.Enable(enable); -} - -void MainDialog::UpdateControlPositions(const SIZE* size) { - // Set client area - win32::Rect rect_client; - if (size == nullptr) { - GetClientRect(&rect_client); - } else { - rect_client.Set(0, 0, size->cx, size->cy); - } - - // Resize rebar - rebar.SendMessage(WM_SIZE, 0, 0); - rect_client.top += rebar.GetBarHeight(); - - // Resize status bar - win32::Rect rcStatus; - statusbar.GetClientRect(&rcStatus); - statusbar.SendMessage(WM_SIZE, 0, 0); - UpdateStatusTimer(); - rect_client.bottom -= rcStatus.Height(); - - // Set sidebar - rect_sidebar_.Set(0, rect_client.top, 140, rect_client.bottom); - // Resize treeview - if (treeview.IsVisible()) { - win32::Rect rect_tree(rect_sidebar_); - rect_tree.Inflate(-ScaleX(WIN_CONTROL_MARGIN), -ScaleY(WIN_CONTROL_MARGIN)); - treeview.SetPosition(nullptr, rect_tree); - } - - // Set content - if (treeview.IsVisible()) { - rect_content_.Subtract(rect_client, rect_sidebar_); - } else { - rect_content_ = rect_client; - } - - // Resize content - AnimeListDialog.SetPosition(nullptr, rect_content_); - HistoryDialog.SetPosition(nullptr, rect_content_); - NowPlayingDialog.SetPosition(nullptr, rect_content_); - SearchDialog.SetPosition(nullptr, rect_content_); - SeasonDialog.SetPosition(nullptr, rect_content_); - StatsDialog.SetPosition(nullptr, rect_content_); - TorrentDialog.SetPosition(nullptr, rect_content_); -} - -void MainDialog::UpdateStatusTimer() { - win32::Rect rect; - GetClientRect(&rect); - - int seconds = Settings.Account.Update.delay - Taiga.ticker_media; - - if (CurrentEpisode.anime_id > anime::ID_UNKNOWN && - seconds > 0 && seconds < Settings.Account.Update.delay && - AnimeDatabase.FindItem(CurrentEpisode.anime_id)->IsUpdateAllowed(CurrentEpisode, true)) { - wstring str = L"List update in " + ToTimeString(seconds); - statusbar.SetPartText(1, str.c_str()); - statusbar.SetPartTipText(1, str.c_str()); - - statusbar.SetPartWidth(0, rect.Width() - ScaleX(160)); - statusbar.SetPartWidth(1, ScaleX(160)); - - } else { - statusbar.SetPartWidth(0, rect.Width()); - statusbar.SetPartWidth(1, 0); - } -} - -void MainDialog::UpdateTip() { - wstring tip = APP_TITLE; - if (CurrentEpisode.anime_id > anime::ID_UNKNOWN) { - auto anime_item = AnimeDatabase.FindItem(CurrentEpisode.anime_id); - tip += L"\nWatching: " + anime_item->GetTitle() + - (!CurrentEpisode.number.empty() ? L" #" + CurrentEpisode.number : L""); - } - Taskbar.Modify(tip.c_str()); -} - -void MainDialog::UpdateTitle() { - wstring title = APP_TITLE; - if (!Settings.Account.MAL.user.empty()) { - title += L" \u2013 " + Settings.Account.MAL.user; - } - if (CurrentEpisode.anime_id > anime::ID_UNKNOWN) { - auto anime_item = AnimeDatabase.FindItem(CurrentEpisode.anime_id); - title += L" \u2013 " + anime_item->GetTitle() + PushString(L" #", CurrentEpisode.number); - if (Settings.Account.Update.out_of_range && - GetEpisodeLow(CurrentEpisode.number) > anime_item->GetMyLastWatchedEpisode() + 1) { - title += L" (out of range)"; - } - } - SetText(title); -} - -// ============================================================================= - -int MainDialog::Navigation::GetCurrentPage() { - return current_page_; -} - -void MainDialog::Navigation::SetCurrentPage(int page, bool add_to_history) { - if (page == current_page_) - return; - - int previous_page = current_page_; - current_page_ = page; - - wstring cue_text, search_text; - switch (current_page_) { - case SIDEBAR_ITEM_ANIMELIST: - case SIDEBAR_ITEM_SEASONS: - parent->search_bar.mode = SEARCH_MODE_MAL; - cue_text = L"Filter list or search MyAnimeList"; - break; - case SIDEBAR_ITEM_NOWPLAYING: - case SIDEBAR_ITEM_HISTORY: - case SIDEBAR_ITEM_STATS: - case SIDEBAR_ITEM_SEARCH: - parent->search_bar.mode = SEARCH_MODE_MAL; - cue_text = L"Search MyAnimeList for anime"; - if (current_page_ == SIDEBAR_ITEM_SEARCH) - search_text = SearchDialog.search_text; - break; - case SIDEBAR_ITEM_FEEDS: - parent->search_bar.mode = SEARCH_MODE_FEED; - cue_text = L"Search for torrents"; - break; - } - if (!parent->search_bar.filters.text.empty()) { - parent->search_bar.filters.text.clear(); - switch (previous_page) { - case SIDEBAR_ITEM_ANIMELIST: - AnimeListDialog.RefreshList(); - AnimeListDialog.RefreshTabs(); - break; - case SIDEBAR_ITEM_SEASONS: - SeasonDialog.RefreshList(); - break; - } - } - parent->edit.SetCueBannerText(cue_text.c_str()); - parent->edit.SetText(search_text); - - #define DISPLAY_PAGE(item, dialog, resource_id) \ - case item: \ - if (!dialog.IsWindow()) dialog.Create(resource_id, parent->GetWindowHandle(), false); \ - parent->UpdateControlPositions(); \ - dialog.Show(); \ - break; - switch (current_page_) { - DISPLAY_PAGE(SIDEBAR_ITEM_NOWPLAYING, NowPlayingDialog, IDD_ANIME_INFO); - DISPLAY_PAGE(SIDEBAR_ITEM_ANIMELIST, AnimeListDialog, IDD_ANIME_LIST); - DISPLAY_PAGE(SIDEBAR_ITEM_HISTORY, HistoryDialog, IDD_HISTORY); - DISPLAY_PAGE(SIDEBAR_ITEM_STATS, StatsDialog, IDD_STATS); - DISPLAY_PAGE(SIDEBAR_ITEM_SEARCH, SearchDialog, IDD_SEARCH); - DISPLAY_PAGE(SIDEBAR_ITEM_SEASONS, SeasonDialog, IDD_SEASON); - DISPLAY_PAGE(SIDEBAR_ITEM_FEEDS, TorrentDialog, IDD_TORRENT); - } - #undef DISPLAY_PAGE - - if (current_page_ != SIDEBAR_ITEM_NOWPLAYING) NowPlayingDialog.Hide(); - if (current_page_ != SIDEBAR_ITEM_ANIMELIST) AnimeListDialog.Hide(); - if (current_page_ != SIDEBAR_ITEM_HISTORY) HistoryDialog.Hide(); - if (current_page_ != SIDEBAR_ITEM_STATS) StatsDialog.Hide(); - if (current_page_ != SIDEBAR_ITEM_SEARCH) SearchDialog.Hide(); - if (current_page_ != SIDEBAR_ITEM_SEASONS) SeasonDialog.Hide(); - if (current_page_ != SIDEBAR_ITEM_FEEDS) TorrentDialog.Hide(); - - parent->treeview.SelectItem(parent->treeview.hti.at(current_page_)); - - UpdateViewMenu(); - Refresh(add_to_history); -} - -void MainDialog::Navigation::GoBack() { - if (index_ > 0) { - index_--; - SetCurrentPage(items_.at(index_), false); - } -} - -void MainDialog::Navigation::GoForward() { - if (index_ < items_.size() - 1) { - index_++; - SetCurrentPage(items_.at(index_), false); - } -} - -void MainDialog::Navigation::Refresh(bool add_to_history) { - if (add_to_history) { - auto it = std::find(items_.begin(), items_.end(), current_page_); - if (it != items_.end()) - items_.erase(it); - - items_.push_back(current_page_); - index_ = items_.size() - 1; - } -} \ No newline at end of file diff --git a/dlg/dlg_settings.cpp b/dlg/dlg_settings.cpp deleted file mode 100644 index 2fb551a6a..000000000 --- a/dlg/dlg_settings.cpp +++ /dev/null @@ -1,557 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2013, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_anime_list.h" -#include "dlg_history.h" -#include "dlg_input.h" -#include "dlg_main.h" -#include "dlg_settings.h" -#include "dlg_stats.h" - -#include "../anime_db.h" -#include "../anime_filter.h" -#include "../announce.h" -#include "../common.h" -#include "../debug.h" -#include "../history.h" -#include "../http.h" -#include "../media.h" -#include "../monitor.h" -#include "../myanimelist.h" -#include "../resource.h" -#include "../settings.h" -#include "../stats.h" -#include "../string.h" -#include "../taiga.h" -#include "../theme.h" - -#include "../win32/win_control.h" -#include "../win32/win_taskdialog.h" - -const WCHAR* SECTION_TITLE[] = { - L" Services", - L" Library", - L" Application", - L" Recognition", - L" Sharing", - L" Torrents" -}; - -class SettingsDialog SettingsDialog; - -// ============================================================================= - -SettingsDialog::SettingsDialog() - : current_section_(SECTION_SERVICES), - current_page_(PAGE_SERVICES_MAL) { - RegisterDlgClass(L"TaigaSettingsW"); - - pages.resize(PAGE_COUNT); - for (size_t i = 0; i < PAGE_COUNT; i++) { - pages[i].index = i; - pages[i].parent = this; - } -} - -void SettingsDialog::SetCurrentSection(int index) { - current_section_ = index; - - if (!IsWindow()) - return; - - SetDlgItemText(IDC_STATIC_TITLE, SECTION_TITLE[index - 1]); - - tab_.DeleteAllItems(); - switch (index) { - case SECTION_SERVICES: - tab_.InsertItem(0, L"MyAnimeList", PAGE_SERVICES_MAL); - break; - case SECTION_LIBRARY: - tab_.InsertItem(0, L"Folders", PAGE_LIBRARY_FOLDERS); - tab_.InsertItem(1, L"Cache", PAGE_LIBRARY_CACHE); - break; - case SECTION_APPLICATION: - tab_.InsertItem(0, L"Anime list", PAGE_APP_LIST); - tab_.InsertItem(1, L"Behavior", PAGE_APP_BEHAVIOR); - tab_.InsertItem(2, L"Connection", PAGE_APP_CONNECTION); - tab_.InsertItem(3, L"Interface", PAGE_APP_INTERFACE); - break; - case SECTION_RECOGNITION: - tab_.InsertItem(0, L"General", PAGE_RECOGNITION_GENERAL); - tab_.InsertItem(1, L"Media players", PAGE_RECOGNITION_MEDIA); - tab_.InsertItem(2, L"Media providers", PAGE_RECOGNITION_STREAM); - break; - case SECTION_SHARING: - tab_.InsertItem(0, L"HTTP", PAGE_SHARING_HTTP); - tab_.InsertItem(1, L"Messenger", PAGE_SHARING_MESSENGER); - tab_.InsertItem(2, L"mIRC", PAGE_SHARING_MIRC); - tab_.InsertItem(3, L"Skype", PAGE_SHARING_SKYPE); - tab_.InsertItem(4, L"Twitter", PAGE_SHARING_TWITTER); - break; - case SECTION_TORRENTS: - tab_.InsertItem(0, L"Discovery", PAGE_TORRENTS_DISCOVERY); - tab_.InsertItem(1, L"Downloads", PAGE_TORRENTS_DOWNLOADS); - tab_.InsertItem(2, L"Filters", PAGE_TORRENTS_FILTERS); - break; - } -} - -void SettingsDialog::SetCurrentPage(int index) { - pages.at(current_page_).Hide(); - - current_page_ = index; - - if (!IsWindow()) - return; - - if (!pages.at(index).IsWindow()) - pages.at(index).Create(); - pages.at(index).Show(); - - for (int i = 0; i < tab_.GetItemCount(); i++) { - if (tab_.GetItemParam(i) == index) { - tab_.SetCurrentlySelected(i); - break; - } - } -} - -// ============================================================================= - -BOOL SettingsDialog::OnInitDialog() { - // Initialize controls - tree_.Attach(GetDlgItem(IDC_TREE_SECTIONS)); - tree_.SetImageList(UI.ImgList24.GetHandle()); - tree_.SetTheme(); - tab_.Attach(GetDlgItem(IDC_TAB_PAGES)); - - // Add tree items - #define TREE_INSERTITEM(s, t, i) \ - tree_.items[s] = tree_.InsertItem(t, i, s, nullptr); - TREE_INSERTITEM(SECTION_SERVICES, L"Services", ICON24_GLOBE); - TREE_INSERTITEM(SECTION_LIBRARY, L"Library", ICON24_LIBRARY); - TREE_INSERTITEM(SECTION_APPLICATION, L"Application", ICON24_APPLICATION); - TREE_INSERTITEM(SECTION_RECOGNITION, L"Recognition", ICON24_RECOGNITION); - TREE_INSERTITEM(SECTION_SHARING, L"Sharing", ICON24_SHARING); - TREE_INSERTITEM(SECTION_TORRENTS, L"Torrents", ICON24_FEED); - #undef TREE_INSERTITEM - - // Set title font - SendDlgItemMessage(IDC_STATIC_TITLE, WM_SETFONT, - reinterpret_cast(UI.font_bold.Get()), TRUE); - - // Select current section and page - int current_page = current_page_; - tree_.SelectItem(tree_.items[current_section_]); - SetCurrentSection(current_section_); - SetCurrentPage(current_page); - - return TRUE; -} - -// ============================================================================= - -void SettingsDialog::OnOK() { - win32::ListView list; - SettingsPage* page = nullptr; - - wstring previous_user = Settings.Account.MAL.user; - wstring previous_theme = Settings.Program.General.theme; - - // Services > MyAnimeList - page = &pages[PAGE_SERVICES_MAL]; - if (page->IsWindow()) { - page->GetDlgItemText(IDC_EDIT_USER, Settings.Account.MAL.user); - page->GetDlgItemText(IDC_EDIT_PASS, Settings.Account.MAL.password); - Settings.Account.MAL.auto_sync = page->IsDlgButtonChecked(IDC_CHECK_START_LOGIN); - } - - // Library > Folders - page = &pages[PAGE_LIBRARY_FOLDERS]; - if (page->IsWindow()) { - list.SetWindowHandle(page->GetDlgItem(IDC_LIST_FOLDERS_ROOT)); - Settings.Folders.root.clear(); - for (int i = 0; i < list.GetItemCount(); i++) { - wstring folder; - list.GetItemText(i, 0, folder); - Settings.Folders.root.push_back(folder); - } - Settings.Folders.watch_enabled = page->IsDlgButtonChecked(IDC_CHECK_FOLDERS_WATCH); - list.SetWindowHandle(nullptr); - } - - // Application > Behavior - page = &pages[PAGE_APP_BEHAVIOR]; - if (page->IsWindow()) { - Settings.Program.General.auto_start = page->IsDlgButtonChecked(IDC_CHECK_AUTOSTART); - Settings.Program.General.close = page->IsDlgButtonChecked(IDC_CHECK_GENERAL_CLOSE); - Settings.Program.General.minimize = page->IsDlgButtonChecked(IDC_CHECK_GENERAL_MINIMIZE); - Settings.Program.StartUp.check_new_version = page->IsDlgButtonChecked(IDC_CHECK_START_VERSION); - Settings.Program.StartUp.check_new_episodes = page->IsDlgButtonChecked(IDC_CHECK_START_CHECKEPS); - Settings.Program.StartUp.minimize = page->IsDlgButtonChecked(IDC_CHECK_START_MINIMIZE); - } - // Application > Connection - page = &pages[PAGE_APP_CONNECTION]; - if (page->IsWindow()) { - page->GetDlgItemText(IDC_EDIT_PROXY_HOST, Settings.Program.Proxy.host); - page->GetDlgItemText(IDC_EDIT_PROXY_USER, Settings.Program.Proxy.user); - page->GetDlgItemText(IDC_EDIT_PROXY_PASS, Settings.Program.Proxy.password); - } - // Application > Interface - page = &pages[PAGE_APP_INTERFACE]; - if (page->IsWindow()) { - page->GetDlgItemText(IDC_COMBO_THEME, Settings.Program.General.theme); - page->GetDlgItemText(IDC_EDIT_EXTERNALLINKS, Settings.Program.General.external_links); - } - // Application > List - page = &pages[PAGE_APP_LIST]; - if (page->IsWindow()) { - Settings.Program.List.double_click = page->GetComboSelection(IDC_COMBO_DBLCLICK); - Settings.Program.List.middle_click = page->GetComboSelection(IDC_COMBO_MDLCLICK); - Settings.Program.List.english_titles = page->IsDlgButtonChecked(IDC_CHECK_LIST_ENGLISH); - Settings.Program.List.highlight = page->IsDlgButtonChecked(IDC_CHECK_HIGHLIGHT); - Settings.Program.List.progress_show_aired = page->IsDlgButtonChecked(IDC_CHECK_LIST_PROGRESS_AIRED); - Settings.Program.List.progress_show_available = page->IsDlgButtonChecked(IDC_CHECK_LIST_PROGRESS_AVAILABLE); - } - - // Recognition > General - page = &pages[PAGE_RECOGNITION_GENERAL]; - if (page->IsWindow()) { - Settings.Account.Update.ask_to_confirm = page->IsDlgButtonChecked(IDC_CHECK_UPDATE_CONFIRM); - Settings.Account.Update.check_player = page->IsDlgButtonChecked(IDC_CHECK_UPDATE_CHECKMP); - Settings.Account.Update.go_to_nowplaying = page->IsDlgButtonChecked(IDC_CHECK_UPDATE_GOTO); - Settings.Account.Update.out_of_range = page->IsDlgButtonChecked(IDC_CHECK_UPDATE_RANGE); - Settings.Account.Update.out_of_root = page->IsDlgButtonChecked(IDC_CHECK_UPDATE_ROOT); - Settings.Account.Update.wait_mp = page->IsDlgButtonChecked(IDC_CHECK_UPDATE_WAITMP); - Settings.Account.Update.delay = page->GetDlgItemInt(IDC_EDIT_DELAY); - Settings.Program.Notifications.recognized = page->IsDlgButtonChecked(IDC_CHECK_NOTIFY_RECOGNIZED); - Settings.Program.Notifications.notrecognized = page->IsDlgButtonChecked(IDC_CHECK_NOTIFY_NOTRECOGNIZED); - } - // Recognition > Media players - page = &pages[PAGE_RECOGNITION_MEDIA]; - if (page->IsWindow()) { - list.SetWindowHandle(page->GetDlgItem(IDC_LIST_MEDIA)); - for (size_t i = 0; i < MediaPlayers.items.size(); i++) - MediaPlayers.items[i].enabled = list.GetCheckState(i); - list.SetWindowHandle(nullptr); - } - // Recognition > Media providers - page = &pages[PAGE_RECOGNITION_STREAM]; - if (page->IsWindow()) { - list.SetWindowHandle(page->GetDlgItem(IDC_LIST_STREAM_PROVIDER)); - Settings.Recognition.Streaming.ann_enabled = list.GetCheckState(0) == TRUE; - Settings.Recognition.Streaming.crunchyroll_enabled = list.GetCheckState(1) == TRUE; - Settings.Recognition.Streaming.veoh_enabled = list.GetCheckState(2) == TRUE; - Settings.Recognition.Streaming.viz_enabled = list.GetCheckState(3) == TRUE; - Settings.Recognition.Streaming.youtube_enabled = list.GetCheckState(4) == TRUE; - list.SetWindowHandle(nullptr); - } - - // Sharing > HTTP - page = &pages[PAGE_SHARING_HTTP]; - if (page->IsWindow()) { - Settings.Announce.HTTP.enabled = page->IsDlgButtonChecked(IDC_CHECK_HTTP); - page->GetDlgItemText(IDC_EDIT_HTTP_URL, Settings.Announce.HTTP.url); - } - // Sharing > Messenger - page = &pages[PAGE_SHARING_MESSENGER]; - if (page->IsWindow()) { - Settings.Announce.MSN.enabled = page->IsDlgButtonChecked(IDC_CHECK_MESSENGER); - } - // Sharing > mIRC - page = &pages[PAGE_SHARING_MIRC]; - if (page->IsWindow()) { - Settings.Announce.MIRC.enabled = page->IsDlgButtonChecked(IDC_CHECK_MIRC); - page->GetDlgItemText(IDC_EDIT_MIRC_SERVICE, Settings.Announce.MIRC.service); - Settings.Announce.MIRC.mode = page->GetCheckedRadioButton(IDC_RADIO_MIRC_CHANNEL1, IDC_RADIO_MIRC_CHANNEL3) + 1; - Settings.Announce.MIRC.multi_server = page->IsDlgButtonChecked(IDC_CHECK_MIRC_MULTISERVER); - Settings.Announce.MIRC.use_action = page->IsDlgButtonChecked(IDC_CHECK_MIRC_ACTION); - page->GetDlgItemText(IDC_EDIT_MIRC_CHANNELS, Settings.Announce.MIRC.channels); - } - // Sharing > Skype - page = &pages[PAGE_SHARING_SKYPE]; - if (page->IsWindow()) { - Settings.Announce.Skype.enabled = page->IsDlgButtonChecked(IDC_CHECK_SKYPE); - } - // Sharing > Twitter - page = &pages[PAGE_SHARING_TWITTER]; - if (page->IsWindow()) { - Settings.Announce.Twitter.enabled = page->IsDlgButtonChecked(IDC_CHECK_TWITTER); - } - - // Torrents > Discovery - page = &pages[PAGE_TORRENTS_DISCOVERY]; - if (page->IsWindow()) { - page->GetDlgItemText(IDC_COMBO_TORRENT_SOURCE, Settings.RSS.Torrent.source); - page->GetDlgItemText(IDC_COMBO_TORRENT_SEARCH, Settings.RSS.Torrent.search_url); - Settings.RSS.Torrent.check_enabled = page->IsDlgButtonChecked(IDC_CHECK_TORRENT_AUTOCHECK); - Settings.RSS.Torrent.check_interval = page->GetDlgItemInt(IDC_EDIT_TORRENT_INTERVAL); - Settings.RSS.Torrent.new_action = page->GetCheckedRadioButton(IDC_RADIO_TORRENT_NEW1, IDC_RADIO_TORRENT_NEW2) + 1; - } - // Torrents > Downloads - page = &pages[PAGE_TORRENTS_DOWNLOADS]; - if (page->IsWindow()) { - Settings.RSS.Torrent.app_mode = page->GetCheckedRadioButton(IDC_RADIO_TORRENT_APP1, IDC_RADIO_TORRENT_APP2) + 1; - page->GetDlgItemText(IDC_EDIT_TORRENT_APP, Settings.RSS.Torrent.app_path); - Settings.RSS.Torrent.set_folder = page->IsDlgButtonChecked(IDC_CHECK_TORRENT_AUTOSETFOLDER); - Settings.RSS.Torrent.use_folder = page->IsDlgButtonChecked(IDC_CHECK_TORRENT_AUTOUSEFOLDER); - page->GetDlgItemText(IDC_COMBO_TORRENT_FOLDER, Settings.RSS.Torrent.download_path); - Settings.RSS.Torrent.create_folder = page->IsDlgButtonChecked(IDC_CHECK_TORRENT_AUTOCREATEFOLDER); - } - // Torrents > Filters - page = &pages[PAGE_TORRENTS_FILTERS]; - if (page->IsWindow()) { - Settings.RSS.Torrent.Filters.global_enabled = page->IsDlgButtonChecked(IDC_CHECK_TORRENT_FILTER); - list.SetWindowHandle(page->GetDlgItem(IDC_LIST_TORRENT_FILTER)); - for (int i = 0; i < list.GetItemCount(); i++) { - FeedFilter* filter = reinterpret_cast(list.GetItemParam(i)); - if (filter) filter->enabled = list.GetCheckState(i) == TRUE; - } - list.SetWindowHandle(nullptr); - Aggregator.filter_manager.filters.clear(); - for (auto it = feed_filters_.begin(); it != feed_filters_.end(); ++it) - Aggregator.filter_manager.filters.push_back(*it); - } - - // Save settings - Settings.Save(); - - // Apply changes - Settings.ApplyChanges(previous_user, previous_theme); - - // End dialog - EndDialog(IDOK); -} - -// ============================================================================= - -INT_PTR SettingsDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - // Set title color - case WM_CTLCOLORSTATIC: { - HDC hdc = reinterpret_cast(wParam); - HWND hwnd_static = reinterpret_cast(lParam); - if (hwnd_static == GetDlgItem(IDC_STATIC_TITLE)) { - ::SetBkMode(hdc, TRANSPARENT); - ::SetTextColor(hdc, ::GetSysColor(COLOR_WINDOW)); - return reinterpret_cast(::GetSysColorBrush(COLOR_APPWORKSPACE)); - } - break; - } - - // Drag window - case WM_ENTERSIZEMOVE: - if (::IsAppThemed() && win32::GetWinVersion() >= win32::VERSION_VISTA) - SetTransparency(200); - break; - case WM_EXITSIZEMOVE: - if (::IsAppThemed() && win32::GetWinVersion() >= win32::VERSION_VISTA) - SetTransparency(255); - break; - - // Taiga, help! Only you can save us! - case WM_HELP: { - OnHelp(reinterpret_cast(lParam)); - return TRUE; - } - - // Forward mouse wheel messages to the list - case WM_MOUSEWHEEL: - switch (current_page_) { - case PAGE_LIBRARY_FOLDERS: - return pages[PAGE_LIBRARY_FOLDERS].SendDlgItemMessage( - IDC_LIST_FOLDERS_ROOT, uMsg, wParam, lParam); - case PAGE_RECOGNITION_MEDIA: - return pages[PAGE_RECOGNITION_MEDIA].SendDlgItemMessage( - IDC_LIST_MEDIA, uMsg, wParam, lParam); - case PAGE_TORRENTS_FILTERS: - return pages[PAGE_TORRENTS_FILTERS].SendDlgItemMessage( - IDC_LIST_TORRENT_FILTER, uMsg, wParam, lParam); - } - break; - } - - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - -void SettingsDialog::OnHelp(LPHELPINFO lphi) { - wstring message, text; - pages.at(current_page_).GetDlgItemText(lphi->iCtrlId, text); - - #define SET_HELP_TEXT(i, m) \ - case i: message = m; break; - switch (lphi->iCtrlId) { - // Library > Folders - SET_HELP_TEXT(IDC_LIST_FOLDERS_ROOT, - L"These folders will be scanned and monitored for new episodes.\n\n" - L"Suppose that you have an HDD like this:\n\n" - L" D:\\\n" - L" \u2514 Anime\n" - L" \u2514 Bleach\n" - L" \u2514 Naruto\n" - L" \u2514 One Piece\n" - L" \u2514 Games\n" - L" \u2514 Music\n\n" - L"In this case, \"D:\\Anime\" is the root folder you should add."); - SET_HELP_TEXT(IDC_CHECK_FOLDERS_WATCH, - L"With this feature on, Taiga instantly detects when a file is added, removed, or renamed under root folders and their subfolders.\n\n" - L"Enabling this feature is recommended."); - // Not available - default: - message = L"There's no help message associated with this item."; - break; - } - #undef SET_HELP_TEXT - - MessageBox(message.c_str(), L"Help", MB_ICONINFORMATION | MB_OK); -} - -LRESULT SettingsDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { - switch (idCtrl) { - case IDC_TREE_SECTIONS: { - switch (pnmh->code) { - // Select section - case TVN_SELCHANGED: { - LPNMTREEVIEW pnmtv = reinterpret_cast(pnmh); - int section_new = pnmtv->itemNew.lParam; - int section_old = pnmtv->itemOld.lParam; - if (section_new != section_old) { - SetCurrentSection(section_new); - SetCurrentPage(tab_.GetItemParam(0)); - } - break; - } - } - break; - } - - case IDC_TAB_PAGES: { - switch (pnmh->code) { - // Select tab - case TCN_SELCHANGE: { - int index = static_cast(tab_.GetItemParam(tab_.GetCurrentlySelected())); - SetCurrentPage(index); - break; - } - } - break; - } - - case IDC_LINK_DEFAULTS: { - switch (pnmh->code) { - // Restore default settings - case NM_CLICK: { - win32::TaskDialog dlg; - dlg.SetWindowTitle(APP_NAME); - dlg.SetMainIcon(TD_ICON_WARNING); - dlg.SetMainInstruction(L"Are you sure you want to restore default settings?"); - dlg.SetContent(L"All your current settings will be lost."); - dlg.AddButton(L"Yes", IDYES); - dlg.AddButton(L"No", IDNO); - dlg.Show(GetWindowHandle()); - if (dlg.GetSelectedButtonID() == IDYES) - Settings.RestoreDefaults(); - return TRUE; - } - } - break; - } - } - - return 0; -} - -LRESULT SettingsDialog::TreeView::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - // Forward mouse wheel messages to parent - case WM_MOUSEWHEEL: - return ::SendMessage(GetParent(), uMsg, wParam, lParam); - } - - return WindowProcDefault(hwnd, uMsg, wParam, lParam); -} - -// ============================================================================= - -int SettingsDialog::AddTorrentFilterToList(HWND hwnd_list, const FeedFilter& filter) { - wstring text; - win32::ListView list = hwnd_list; - int index = list.GetItemCount(); - int group = filter.anime_ids.empty() ? 0 : 1; - - int icon = ICON16_FUNNEL; - switch (filter.action) { - case FEED_FILTER_ACTION_DISCARD: icon = ICON16_FUNNEL_CROSS; break; - case FEED_FILTER_ACTION_SELECT: icon = ICON16_FUNNEL_TICK; break; - case FEED_FILTER_ACTION_PREFER: icon = ICON16_FUNNEL_PLUS; break; - } - - // Insert item - index = list.InsertItem(index, group, icon, 0, nullptr, filter.name.c_str(), - reinterpret_cast(&filter)); - list.SetCheckState(index, filter.enabled); - list.SetWindowHandle(nullptr); - - return index; -} - -void SettingsDialog::RefreshCache() { - wstring text; - Stats.CalculateLocalData(); - SettingsPage& page = pages[PAGE_LIBRARY_CACHE]; - - // History - text = ToWstr(static_cast(History.items.size())) + L" item(s)"; - page.SetDlgItemText(IDC_STATIC_CACHE1, text.c_str()); - - // Image files - text = ToWstr(Stats.image_count) + L" item(s), " + ToSizeString(Stats.image_size); - page.SetDlgItemText(IDC_STATIC_CACHE2, text.c_str()); - - // Torrent files - text = ToWstr(Stats.torrent_count) + L" item(s), " + ToSizeString(Stats.torrent_size); - page.SetDlgItemText(IDC_STATIC_CACHE3, text.c_str()); -} - -void SettingsDialog::RefreshTorrentFilterList(HWND hwnd_list) { - win32::ListView list = hwnd_list; - list.DeleteAllItems(); - - for (auto it = feed_filters_.begin(); it != feed_filters_.end(); ++it) { - AddTorrentFilterToList(hwnd_list, *it); - } - - list.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); - list.SetWindowHandle(nullptr); -} - -void SettingsDialog::RefreshTwitterLink() { - wstring text; - - if (Settings.Announce.Twitter.user.empty()) { - text = L"Taiga is not authorized to post to your Twitter account yet."; - } else { - text = L"Taiga is authorized to post to this Twitter account: "; - text += L"" + Settings.Announce.Twitter.user + L""; - } - - pages[PAGE_SHARING_TWITTER].SetDlgItemText(IDC_LINK_TWITTER, text.c_str()); -} \ No newline at end of file diff --git a/encryption.cpp b/encryption.cpp deleted file mode 100644 index 07c5a5bd5..000000000 --- a/encryption.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "string.h" - -#define MIN_LENGHT 16 -#define SIMPLE_KEY L"Tenori Taiga" - -// ============================================================================= - -wstring XOR(wstring str, wstring key) { - for (unsigned int i = 0; i < str.length(); i++) { - str[i] = str[i] ^ key[i % key.length()]; - } - return str; -} - -wstring SimpleEncrypt(wstring str) { - // Set minimum length - if (str.length() > 0 && str.length() < MIN_LENGHT) { - str.append(MIN_LENGHT - str.length(), '?'); - } - // Encrypt - str = XOR(str, SIMPLE_KEY); - // Convert to hexadecimal string - wstring buffer; - for (unsigned int i = 0; i < str.length(); i++) { - wchar_t c[32] = {'\0'}; - _itow_s(str[i], c, 32, 16); - if (wcslen(c) == 1) buffer.push_back('0'); - buffer += c; - } - ToUpper(buffer); - // Return - return buffer; -} - -wstring SimpleDecrypt(wstring str) { - // Convert from hexadecimal string - wstring buffer; - for (unsigned int i = 0; i < str.length(); i = i + 2) { - wchar_t c = static_cast(wcstoul(str.substr(i, 2).c_str(), NULL, 16)); - buffer.push_back(c); - } - // Decrypt - buffer = XOR(buffer, SIMPLE_KEY); - // Trim - TrimRight(buffer, L"?"); - // Return - return buffer; -} \ No newline at end of file diff --git a/feed.cpp b/feed.cpp deleted file mode 100644 index ca898a8b8..000000000 --- a/feed.cpp +++ /dev/null @@ -1,416 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "feed.h" - -#include "anime_db.h" -#include "common.h" -#include "foreach.h" -#include "gfx.h" -#include "recognition.h" -#include "resource.h" -#include "settings.h" -#include "string.h" -#include "taiga.h" -#include "xml.h" - -#include "dlg/dlg_main.h" -#include "dlg/dlg_torrent.h" - -#include "win32/win_taskbar.h" - -class Aggregator Aggregator; - -// ============================================================================= - -void FeedItem::Discard(int option) { - switch (option) { - default: - case FEED_FILTER_OPTION_DEFAULT: - state = FEEDITEM_DISCARDED_NORMAL; - break; - case FEED_FILTER_OPTION_DEACTIVATE: - state = FEEDITEM_DISCARDED_INACTIVE; - break; - case FEED_FILTER_OPTION_HIDE: - state = FEEDITEM_DISCARDED_HIDDEN; - break; - } -} - -bool FeedItem::IsDiscarded() const { - switch (state) { - case FEEDITEM_DISCARDED_NORMAL: - case FEEDITEM_DISCARDED_INACTIVE: - case FEEDITEM_DISCARDED_HIDDEN: - return true; - default: - return false; - } -} - -bool FeedItem::operator<(const FeedItem& item) const { - // Initialize priority list - static const int state_priorities[] = {1, 2, 3, 4, 0}; - - // Sort items by the priority of their state - return state_priorities[this->state] < state_priorities[item.state]; -} - -// ============================================================================= - -Feed::Feed() - : category(0), - download_index(-1), - ticker(0), - icon_(nullptr) { -} - -Feed::~Feed() { - if (icon_) { - DestroyIcon(icon_); - icon_ = nullptr; - } -} - -bool Feed::Check(const wstring& source, bool automatic) { - // Reset ticker before checking the source so we don't fall into a loop - ticker = 0; - if (source.empty()) return false; - link = source; - - switch (category) { - case FEED_CATEGORY_LINK: - // Disable torrent dialog input - TorrentDialog.EnableInput(false); - break; - } - - return client.Get(win32::Url(link), GetDataPath() + L"feed.xml", - automatic ? HTTP_Feed_CheckAuto : HTTP_Feed_Check, - reinterpret_cast(this)); -} - -bool Feed::Download(int index) { - if (category != FEED_CATEGORY_LINK) - return false; - - DWORD dwMode = HTTP_Feed_Download; - if (index == -1) { - for (size_t i = 0; i < items.size(); i++) { - if (items[i].state == FEEDITEM_SELECTED) { - dwMode = HTTP_Feed_DownloadAll; - index = i; - break; - } - } - } - if (index < 0 || index > static_cast(items.size())) return false; - download_index = index; - - MainDialog.ChangeStatus(L"Downloading \"" + items[index].title + L"\"..."); - TorrentDialog.EnableInput(false); - - wstring file = items[index].title + L".torrent"; - ValidateFileName(file); - file = GetDataPath() + file; - - return client.Get(win32::Url(items[index].link), file, dwMode, - reinterpret_cast(this)); -} - -bool Feed::ExamineData() { - for (size_t i = 0; i < items.size(); i++) { - // Examine title and compare with anime list items - Meow.ExamineTitle(items[i].title, items[i].episode_data, true, true, true, true, false); - Meow.MatchDatabase(items[i].episode_data, true, true); - - // Update last aired episode number - if (items[i].episode_data.anime_id > anime::ID_UNKNOWN) { - auto anime_item = AnimeDatabase.FindItem(items[i].episode_data.anime_id); - int episode_number = GetEpisodeHigh(items[i].episode_data.number); - anime_item->SetLastAiredEpisodeNumber(episode_number); - } - } - - // Filter - Aggregator.filter_manager.MarkNewEpisodes(*this); - // Preferences have lower priority, so we need to handle other filters - // first in order to avoid discarding items that we actually want. - Aggregator.filter_manager.Filter(*this, false); - Aggregator.filter_manager.Filter(*this, true); - // Archived items must be discarded after other filters are processed. - Aggregator.filter_manager.FilterArchived(*this); - - // Sort items - std::stable_sort(items.begin(), items.end()); - // Re-assign item indexes - for (size_t i = 0; i < items.size(); i++) - items.at(i).index = i; - - return Aggregator.filter_manager.IsItemDownloadAvailable(*this); -} - -wstring Feed::GetDataPath() { - wstring path = Taiga.GetDataPath() + L"feed\\"; - if (!link.empty()) { - win32::Url url(link); - path += Base64Encode(url.Host, true) + L"\\"; - } - return path; -} - -HICON Feed::GetIcon() { - if (link.empty()) return NULL; - if (icon_) { - DestroyIcon(icon_); - icon_ = nullptr; - } - - wstring path = GetDataPath() + L"favicon.ico"; - - if (FileExists(path)) { - icon_ = GdiPlus.LoadIcon(path); - return icon_; - } else { - win32::Url url(link + L"/favicon.ico"); - client.Get(url, path, HTTP_Feed_DownloadIcon, reinterpret_cast(this)); - return NULL; - } -} - -bool Feed::Load() { - // Initialize - wstring file = GetDataPath() + L"feed.xml"; - items.clear(); - - // Load XML file - xml_document doc; - xml_parse_result result = doc.load_file(file.c_str()); - if (result.status != status_ok) { - return false; - } - - // Read channel information - xml_node channel = doc.child(L"rss").child(L"channel"); - title = XML_ReadStrValue(channel, L"title"); - link = XML_ReadStrValue(channel, L"link"); - description = XML_ReadStrValue(channel, L"description"); - - // Read items - for (xml_node item = channel.child(L"item"); item; item = item.next_sibling(L"item")) { - // Read data - items.resize(items.size() + 1); - items.back().index = items.size() - 1; - items.back().category = XML_ReadStrValue(item, L"category"); - items.back().title = XML_ReadStrValue(item, L"title"); - items.back().link = XML_ReadStrValue(item, L"link"); - items.back().description = XML_ReadStrValue(item, L"description"); - - // Remove if title or link is empty - if (category == FEED_CATEGORY_LINK) { - if (items.back().title.empty() || items.back().link.empty()) { - items.pop_back(); - continue; - } - } - - // Clean up title - DecodeHtmlEntities(items.back().title); - Replace(items.back().title, L"\\'", L"'"); - // Clean up description - Replace(items.back().description, L"
", L"\n"); - Replace(items.back().description, L"
", L"\n"); - StripHtmlTags(items.back().description); - DecodeHtmlEntities(items.back().description); - Trim(items.back().description, L" \n"); - Aggregator.ParseDescription(items.back(), link); - Replace(items.back().description, L"\n", L" | "); - // Get download link - if (InStr(items.back().link, L"nyaatorrents", 0, true) > -1) { - Replace(items.back().link, L"torrentinfo", L"download"); - } - } - - return true; -} - -// ============================================================================= - -Aggregator::Aggregator() { - // Add torrent feed - feeds.resize(feeds.size() + 1); - feeds.back().category = FEED_CATEGORY_LINK; -} - -Feed* Aggregator::Get(int category) { - foreach_(it, feeds) - if (it->category == category) - return &(*it); - - return nullptr; -} - -bool Aggregator::Notify(const Feed& feed) { - wstring tip_text; - wstring tip_title = L"New torrents available"; - wstring tip_format = L"%title%$if(%episode%, #%episode%)\n"; - - foreach_(it, feed.items) - if (it->state == FEEDITEM_SELECTED) - tip_text += L"\u00BB " + ReplaceVariables(tip_format, it->episode_data); - - if (tip_text.empty()) - return false; - - tip_text += L"Click to see all."; - tip_text = LimitText(tip_text, 255); - Taiga.current_tip_type = TIPTYPE_TORRENT; - Taskbar.Tip(L"", L"", 0); - Taskbar.Tip(tip_text.c_str(), tip_title.c_str(), NIIF_INFO); - - return true; -} - -bool Aggregator::SearchArchive(const wstring& file) { - for (size_t i = 0; i < file_archive.size(); i++) - if (file_archive[i] == file) - return true; - - return false; -} - -void Aggregator::ParseDescription(FeedItem& feed_item, const wstring& source) { - // AnimeSuki - if (InStr(source, L"animesuki", 0, true) > -1) { - wstring size_str = L"Filesize: "; - vector description_vector; - Split(feed_item.description, L"\n", description_vector); - if (description_vector.size() > 2) { - feed_item.episode_data.file_size = description_vector[2].substr(size_str.length()); - } - if (description_vector.size() > 1) { - feed_item.description = description_vector[0] + L" " + description_vector[1]; - return; - } - feed_item.description.clear(); - - // Baka-Updates - } else if (InStr(source, L"baka-updates", 0, true) > -1) { - int index_begin = 0, index_end = feed_item.description.length(); - index_begin = InStr(feed_item.description, L"Released on"); - if (index_begin > -1) index_end -= index_begin; - if (index_begin == -1) index_begin = 0; - feed_item.description = feed_item.description.substr(index_begin, index_end); - - // NyaaTorrents - } else if (InStr(source, L"nyaa", 0, true) > -1) { - feed_item.episode_data.file_size = InStr(feed_item.description, L" - ", L" - "); - Erase(feed_item.description, feed_item.episode_data.file_size); - Replace(feed_item.description, L"- -", L"-"); - - // TokyoTosho - } else if (InStr(source, L"tokyotosho", 0, true) > -1) { - wstring size_str = L"Size: ", comment_str = L"Comment: "; - vector description_vector; - Split(feed_item.description, L"\n", description_vector); - feed_item.description.clear(); - for (auto it = description_vector.begin(); it != description_vector.end(); ++it) { - if (StartsWith(*it, size_str)) { - feed_item.episode_data.file_size = it->substr(size_str.length()); - } else if (StartsWith(*it, comment_str)) { - feed_item.description = it->substr(comment_str.length()); - } else if (InStr(*it, L"magnet:?") > -1) { - feed_item.magnet_link = L"magnet:?" + - InStr(*it, L"Magnet Link"); - } - } - - // Yahoo! Pipes - } else if (InStr(source, L"pipes.yahoo.com", 0, true) > -1) { - Erase(feed_item.title, L" "); - } -} - -bool Aggregator::LoadArchive() { - // Initialize - wstring folder = Taiga.GetDataPath() + L"feed\\"; - wstring file = folder + L"history.xml"; - CreateDirectory(folder.c_str(), NULL); - - // Load XML file - xml_document doc; - xml_parse_result result = doc.load_file(file.c_str()); - - // Read discarded - file_archive.clear(); - xml_node archive = doc.child(L"archive"); - for (xml_node item = archive.child(L"item"); item; item = item.next_sibling(L"item")) { - file_archive.push_back(item.attribute(L"title").value()); - } - - return result.status == status_ok; -} - -bool Aggregator::SaveArchive() { - // Initialize - xml_document doc; - xml_node archive = doc.append_child(L"archive"); - - if (Settings.RSS.Torrent.Filters.archive_maxcount > 0) { - // Items - size_t length = file_archive.size(); - size_t i = 0; - if (length > Settings.RSS.Torrent.Filters.archive_maxcount) - i = length - Settings.RSS.Torrent.Filters.archive_maxcount; - for ( ; i < file_archive.size(); i++) { - xml_node xml_item = archive.append_child(L"item"); - xml_item.append_attribute(L"title") = file_archive[i].c_str(); - } - } - - // Save file - wstring file = Taiga.GetDataPath() + L"feed\\history.xml"; - return doc.save_file(file.c_str(), L"\x09", format_default | format_write_bom); -} - -// ============================================================================= - -bool Aggregator::CompareFeedItems(const GenericFeedItem& item1, const GenericFeedItem& item2) { - // Check for guid element first - if (item1.is_permalink && item2.is_permalink) { - if (!item1.guid.empty() || !item2.guid.empty()) { - if (item1.guid == item2.guid) return true; - } - } - - // Fallback to link element - if (!item1.link.empty() || !item2.link.empty()) { - if (item1.link == item2.link) return true; - } - - // Fallback to title element - if (!item1.title.empty() || !item2.title.empty()) { - if (item1.title == item2.title) return true; - } - - // items are different - return false; -} \ No newline at end of file diff --git a/feed.h b/feed.h deleted file mode 100644 index 314a4ff69..000000000 --- a/feed.h +++ /dev/null @@ -1,293 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef FEED_H -#define FEED_H - -#include "std.h" -#include "anime_episode.h" -#include "http.h" - -// ============================================================================= - -enum FeedItemState { - FEEDITEM_BLANK, - FEEDITEM_DISCARDED_NORMAL, - FEEDITEM_DISCARDED_INACTIVE, - FEEDITEM_DISCARDED_HIDDEN, - FEEDITEM_SELECTED -}; - -class GenericFeedItem { -public: - GenericFeedItem() : is_permalink(true) {} - virtual ~GenericFeedItem() {} - - wstring title, link, description, - author, category, comments, enclosure, guid, pub_date, source; - bool is_permalink; -}; - -class FeedItem : public GenericFeedItem { -public: - FeedItem() : state(FEEDITEM_BLANK) {} - virtual ~FeedItem() {}; - - void Discard(int option); - bool IsDiscarded() const; - - bool operator<(const FeedItem& item) const; - - int index; - wstring magnet_link; - FeedItemState state; - - class EpisodeData : public anime::Episode { - public: - EpisodeData() : new_episode(false) {} - wstring file_size; - bool new_episode; - } episode_data; -}; - -// ============================================================================= - -enum FeedCategory { - // Broadcatching for torrent files and DDL - FEED_CATEGORY_LINK, - // News around the web - FEED_CATEGORY_TEXT, - // Airing times for anime titles - FEED_CATEGORY_TIME -}; - -enum TorrentCategory { - TORRENT_ANIME, - TORRENT_BATCH, - TORRENT_OTHER -}; - -class GenericFeed { -public: - // Required channel elements - wstring title, link, description; - - // Optional channel elements - // (see www.rssboard.org/rss-specification for more information) - /* - wstring language, copyright, managingEditor, webMaster, pubDate, - lastBuildDate, category, generator, docs, cloud, ttl, image, - rating, textInput, skipHours, skipDays; - */ - - // Feed items - vector items; -}; - -class Feed : public GenericFeed { -public: - Feed(); - virtual ~Feed(); - - bool Check(const wstring& source, bool automatic = false); - bool Download(int index); - bool ExamineData(); - wstring GetDataPath(); - HICON GetIcon(); - bool Load(); - -public: - int category, download_index, ticker; - wstring title, link; - HttpClient client; - -private: - HICON icon_; -}; - -// ============================================================================= - -enum FeedFilterElement { - FEED_FILTER_ELEMENT_META_ID, - FEED_FILTER_ELEMENT_META_STATUS, - FEED_FILTER_ELEMENT_META_TYPE, - FEED_FILTER_ELEMENT_META_EPISODES, - FEED_FILTER_ELEMENT_META_DATE_START, - FEED_FILTER_ELEMENT_META_DATE_END, - FEED_FILTER_ELEMENT_USER_STATUS, - FEED_FILTER_ELEMENT_LOCAL_EPISODE_AVAILABLE, - FEED_FILTER_ELEMENT_EPISODE_TITLE, - FEED_FILTER_ELEMENT_EPISODE_NUMBER, - FEED_FILTER_ELEMENT_EPISODE_VERSION, - FEED_FILTER_ELEMENT_EPISODE_GROUP, - FEED_FILTER_ELEMENT_EPISODE_VIDEO_RESOLUTION, - FEED_FILTER_ELEMENT_EPISODE_VIDEO_TYPE, - FEED_FILTER_ELEMENT_FILE_TITLE, - FEED_FILTER_ELEMENT_FILE_CATEGORY, - FEED_FILTER_ELEMENT_FILE_DESCRIPTION, - FEED_FILTER_ELEMENT_FILE_LINK, - FEED_FILTER_ELEMENT_COUNT -}; - -enum FeedFilterOperator { - FEED_FILTER_OPERATOR_EQUALS, - FEED_FILTER_OPERATOR_NOTEQUALS, - FEED_FILTER_OPERATOR_ISGREATERTHAN, - FEED_FILTER_OPERATOR_ISGREATERTHANOREQUALTO, - FEED_FILTER_OPERATOR_ISLESSTHAN, - FEED_FILTER_OPERATOR_ISLESSTHANOREQUALTO, - FEED_FILTER_OPERATOR_BEGINSWITH, - FEED_FILTER_OPERATOR_ENDSWITH, - FEED_FILTER_OPERATOR_CONTAINS, - FEED_FILTER_OPERATOR_NOTCONTAINS, - FEED_FILTER_OPERATOR_COUNT -}; - -enum FeedFilterMatch { - FEED_FILTER_MATCH_ALL, - FEED_FILTER_MATCH_ANY -}; - -class FeedFilterCondition { -public: - FeedFilterCondition() : element(0), op(0) {} - virtual ~FeedFilterCondition() {} - FeedFilterCondition& operator=(const FeedFilterCondition& condition); - - void Reset(); - -public: - int element; - int op; - wstring value; -}; - -enum FeedFilterAction { - FEED_FILTER_ACTION_DISCARD, - FEED_FILTER_ACTION_SELECT, - FEED_FILTER_ACTION_PREFER -}; - -enum FeedFilterOption { - FEED_FILTER_OPTION_DEFAULT, - FEED_FILTER_OPTION_DEACTIVATE, - FEED_FILTER_OPTION_HIDE -}; - -class FeedFilter { -public: - FeedFilter() : action(0), enabled(true), match(FEED_FILTER_MATCH_ALL), option(FEED_FILTER_OPTION_DEFAULT) {} - virtual ~FeedFilter() {} - FeedFilter& operator=(const FeedFilter& filter); - - void AddCondition(int element, int op, const wstring& value); - void Filter(Feed& feed, FeedItem& item, bool recursive); - void Reset(); - -public: - wstring name; - bool enabled; - int action, match, option; - vector anime_ids; - vector conditions; -}; - -class FeedFilterPreset { -public: - FeedFilterPreset() : is_default(false) {} - wstring description; - FeedFilter filter; - bool is_default; -}; - -enum FeedFilterShortcodeType { - FEED_FILTER_SHORTCODE_ACTION, - FEED_FILTER_SHORTCODE_ELEMENT, - FEED_FILTER_SHORTCODE_MATCH, - FEED_FILTER_SHORTCODE_OPERATOR, - FEED_FILTER_SHORTCODE_OPTION -}; - -class FeedFilterManager { -public: - FeedFilterManager(); - - void InitializePresets(); - void InitializeShortcodes(); - - void AddPresets(); - void AddFilter(int action, int match, int option, bool enabled, const wstring& name); - void Cleanup(); - void Filter(Feed& feed, bool preferences); - void FilterArchived(Feed& feed); - bool IsItemDownloadAvailable(Feed& feed); - void MarkNewEpisodes(Feed& feed); - - wstring CreateNameFromConditions(const FeedFilter& filter); - wstring TranslateCondition(const FeedFilterCondition& condition); - wstring TranslateConditions(const FeedFilter& filter, size_t index); - wstring TranslateElement(int element); - wstring TranslateOperator(int op); - wstring TranslateValue(const FeedFilterCondition& condition); - wstring TranslateMatching(int match); - wstring TranslateAction(int action); - wstring TranslateOption(int option); - - wstring GetShortcodeFromIndex(FeedFilterShortcodeType type, int index); - int GetIndexFromShortcode(FeedFilterShortcodeType type, const wstring& shortcode); - -public: - vector filters; - vector presets; - -private: - std::map action_shortcodes_; - std::map element_shortcodes_; - std::map match_shortcodes_; - std::map operator_shortcodes_; - std::map option_shortcodes_; -}; - -// ============================================================================= - -class Aggregator { -public: - Aggregator(); - virtual ~Aggregator() {} - - Feed* Get(int category); - - bool Notify(const Feed& feed); - void ParseDescription(FeedItem& feed_item, const wstring& source); - - bool LoadArchive(); - bool SaveArchive(); - bool SearchArchive(const wstring& file); - -private: - bool CompareFeedItems(const GenericFeedItem& item1, const GenericFeedItem& item2); - -public: - vector feeds; - vector file_archive; - FeedFilterManager filter_manager; -}; - -extern Aggregator Aggregator; - -#endif // FEED_H \ No newline at end of file diff --git a/feed_filter.cpp b/feed_filter.cpp deleted file mode 100644 index bcbdd4b6a..000000000 --- a/feed_filter.cpp +++ /dev/null @@ -1,773 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "feed.h" - -#include "anime.h" -#include "anime_db.h" -#include "common.h" -#include "foreach.h" -#include "myanimelist.h" -#include "logger.h" -#include "settings.h" -#include "string.h" - -// ============================================================================= - -bool EvaluateCondition(const FeedFilterCondition& condition, const FeedItem& item) { - bool is_numeric = false; - wstring element, value = ReplaceVariables(condition.value, item.episode_data); - auto anime = AnimeDatabase.FindItem(item.episode_data.anime_id); - - switch (condition.element) { - case FEED_FILTER_ELEMENT_FILE_TITLE: - element = item.title; - break; - case FEED_FILTER_ELEMENT_FILE_CATEGORY: - element = item.category; - break; - case FEED_FILTER_ELEMENT_FILE_DESCRIPTION: - element = item.description; - break; - case FEED_FILTER_ELEMENT_FILE_LINK: - element = item.link; - break; - case FEED_FILTER_ELEMENT_META_ID: - if (anime) element = ToWstr(anime->GetId()); - is_numeric = true; - break; - case FEED_FILTER_ELEMENT_EPISODE_TITLE: - element = item.episode_data.title; - break; - case FEED_FILTER_ELEMENT_META_DATE_START: - if (anime) element = anime->GetDate(anime::DATE_START); - break; - case FEED_FILTER_ELEMENT_META_DATE_END: - if (anime) element = anime->GetDate(anime::DATE_END); - break; - case FEED_FILTER_ELEMENT_META_EPISODES: - if (anime) element = ToWstr(anime->GetEpisodeCount()); - is_numeric = true; - break; - case FEED_FILTER_ELEMENT_META_STATUS: - if (anime) element = ToWstr(anime->GetAiringStatus()); - is_numeric = true; - break; - case FEED_FILTER_ELEMENT_META_TYPE: - if (anime) element = ToWstr(anime->GetType()); - is_numeric = true; - break; - case FEED_FILTER_ELEMENT_USER_STATUS: - if (anime) element = ToWstr(anime->GetMyStatus()); - is_numeric = true; - break; - case FEED_FILTER_ELEMENT_EPISODE_NUMBER: - element = ToWstr(GetEpisodeHigh(item.episode_data.number)); - is_numeric = true; - break; - case FEED_FILTER_ELEMENT_EPISODE_VERSION: - element = item.episode_data.version; - if (element.empty()) element = L"1"; - is_numeric = true; - break; - case FEED_FILTER_ELEMENT_LOCAL_EPISODE_AVAILABLE: - if (anime) element = ToWstr(anime->IsEpisodeAvailable( - GetEpisodeHigh(item.episode_data.number))); - is_numeric = true; - break; - case FEED_FILTER_ELEMENT_EPISODE_GROUP: - element = item.episode_data.group; - break; - case FEED_FILTER_ELEMENT_EPISODE_VIDEO_RESOLUTION: - element = item.episode_data.resolution; - break; - case FEED_FILTER_ELEMENT_EPISODE_VIDEO_TYPE: - element = item.episode_data.video_type; - break; - } - - switch (condition.op) { - case FEED_FILTER_OPERATOR_EQUALS: - if (is_numeric) { - if (IsEqual(value, L"True")) return ToInt(element) == TRUE; - return ToInt(element) == ToInt(value); - } else { - if (condition.element == FEED_FILTER_ELEMENT_EPISODE_VIDEO_RESOLUTION) { - return TranslateResolution(element) == TranslateResolution(condition.value); - } else { - return IsEqual(element, value); - } - } - case FEED_FILTER_OPERATOR_NOTEQUALS: - if (is_numeric) { - if (IsEqual(value, L"True")) return ToInt(element) == TRUE; - return ToInt(element) != ToInt(value); - } else { - if (condition.element == FEED_FILTER_ELEMENT_EPISODE_VIDEO_RESOLUTION) { - return TranslateResolution(element) != TranslateResolution(condition.value); - } else { - return !IsEqual(element, value); - } - } - case FEED_FILTER_OPERATOR_ISGREATERTHAN: - if (is_numeric) { - return ToInt(element) > ToInt(value); - } else { - if (condition.element == FEED_FILTER_ELEMENT_EPISODE_VIDEO_RESOLUTION) { - return TranslateResolution(element) > TranslateResolution(condition.value); - } else { - return CompareStrings(element, condition.value) > 0; - } - } - case FEED_FILTER_OPERATOR_ISGREATERTHANOREQUALTO: - if (is_numeric) { - return ToInt(element) >= ToInt(value); - } else { - if (condition.element == FEED_FILTER_ELEMENT_EPISODE_VIDEO_RESOLUTION) { - return TranslateResolution(element) >= TranslateResolution(condition.value); - } else { - return CompareStrings(element, condition.value) >= 0; - } - } - case FEED_FILTER_OPERATOR_ISLESSTHAN: - if (is_numeric) { - return ToInt(element) < ToInt(value); - } else { - if (condition.element == FEED_FILTER_ELEMENT_EPISODE_VIDEO_RESOLUTION) { - return TranslateResolution(element) < TranslateResolution(condition.value); - } else { - return CompareStrings(element, condition.value) < 0; - } - } - case FEED_FILTER_OPERATOR_ISLESSTHANOREQUALTO: - if (is_numeric) { - return ToInt(element) <= ToInt(value); - } else { - if (condition.element == FEED_FILTER_ELEMENT_EPISODE_VIDEO_RESOLUTION) { - return TranslateResolution(element) <= TranslateResolution(condition.value); - } else { - return CompareStrings(element, condition.value) <= 0; - } - } - case FEED_FILTER_OPERATOR_BEGINSWITH: - return StartsWith(element, value); - case FEED_FILTER_OPERATOR_ENDSWITH: - return EndsWith(element, value); - case FEED_FILTER_OPERATOR_CONTAINS: - return InStr(element, value, 0, true) > -1; - case FEED_FILTER_OPERATOR_NOTCONTAINS: - return InStr(element, value, 0, true) == -1; - } - - return false; -} - -// ============================================================================= - -FeedFilterCondition& FeedFilterCondition::operator=(const FeedFilterCondition& condition) { - element = condition.element; - op = condition.op; - value = condition.value; - - return *this; -} - -void FeedFilterCondition::Reset() { - element = FEED_FILTER_ELEMENT_FILE_TITLE; - op = FEED_FILTER_OPERATOR_EQUALS; - value.clear(); -} - -// ============================================================================= - -FeedFilter& FeedFilter::operator=(const FeedFilter& filter) { - action = filter.action; - enabled = filter.enabled; - match = filter.match; - name = filter.name; - option = filter.option; - - conditions.resize(filter.conditions.size()); - std::copy(filter.conditions.begin(), filter.conditions.end(), conditions.begin()); - - anime_ids.resize(filter.anime_ids.size()); - std::copy(filter.anime_ids.begin(), filter.anime_ids.end(), anime_ids.begin()); - - return *this; -} - -void FeedFilter::AddCondition(int element, int op, const wstring& value) { - conditions.resize(conditions.size() + 1); - conditions.back().element = element; - conditions.back().op = op; - conditions.back().value = value; -} - -void FeedFilter::Filter(Feed& feed, FeedItem& item, bool recursive) { - if (!enabled) - return; - - // No need to filter if the item was discarded before - if (item.IsDiscarded()) - return; - - if (!anime_ids.empty()) { - bool apply_filter = false; - foreach_(id, anime_ids) { - if (*id == item.episode_data.anime_id) { - apply_filter = true; - break; - } - } - if (!apply_filter) - return; // Filter doesn't apply to this item - } - - bool matched = false; - size_t condition_index = 0; - - switch (match) { - case FEED_FILTER_MATCH_ALL: - matched = true; - for (size_t i = 0; i < conditions.size(); i++) { - if (!EvaluateCondition(conditions[i], item)) { - matched = false; - condition_index = i; - break; - } - } - break; - case FEED_FILTER_MATCH_ANY: - matched = false; - for (size_t i = 0; i < conditions.size(); i++) { - if (EvaluateCondition(conditions[i], item)) { - matched = true; - condition_index = i; - break; - } - } - break; - } - - switch (action) { - case FEED_FILTER_ACTION_DISCARD: - if (matched) { - // Discard matched items, regardless of their previous state - item.Discard(option); - } else { - return; // Filter doesn't apply to this item - } - break; - - case FEED_FILTER_ACTION_SELECT: - if (matched) { - // Select matched items, if they were not discarded before - item.state = FEEDITEM_SELECTED; - } else { - return; // Filter doesn't apply to this item - } - break; - - case FEED_FILTER_ACTION_PREFER: { - if (recursive) { - if (matched) { - foreach_(it, feed.items) { - // Do not bother if the item was discarded before - if (it->IsDiscarded()) continue; - // Do not filter the same item again - if (it->index == item.index) continue; - // Is it the same title? - if (it->episode_data.anime_id == anime::ID_NOTINLIST) { - if (!IsEqual(it->episode_data.title, item.episode_data.title)) continue; - } else { - if (it->episode_data.anime_id != item.episode_data.anime_id) continue; - } - // Is it the same episode? - if (it->episode_data.number != item.episode_data.number) continue; - // Is it the same group? - if (!IsEqual(it->episode_data.group, item.episode_data.group)) continue; - // Try applying the same filter - Filter(feed, *it, false); - } - } - // Filters are strong if they're limited, weak otherwise - bool strong_preference = !anime_ids.empty(); - if (strong_preference) { - if (matched) { - // Select matched items, if they were not discarded before - item.state = FEEDITEM_SELECTED; - } else { - // Discard mismatched items, regardless of their previous state - item.Discard(option); - } - } else { - return; // Filter doesn't apply to this item - } - } else { - // The fact that we're here means that the preference filter matched an - // item before, and now we're checking other items for mismatches. At - // this point, we don't care whether the preference is weak or strong. - if (!matched) { - // Discard mismatched items, regardless of their previous state - item.Discard(option); - } else { - return; // Filter doesn't apply to this item - } - } - break; - } - } - -#ifdef _DEBUG - wstring filter_text = - (item.IsDiscarded() ? L"!FILTER :: " : L"FILTER :: ") + - Aggregator.filter_manager.TranslateConditions(*this, condition_index); - item.description = filter_text + L" -- " + item.description; -#endif -} - -void FeedFilter::Reset() { - enabled = true; - action = FEED_FILTER_ACTION_DISCARD; - match = FEED_FILTER_MATCH_ALL; - anime_ids.clear(); - conditions.clear(); - name.clear(); -} - -// ============================================================================= - -FeedFilterManager::FeedFilterManager() { - InitializePresets(); - InitializeShortcodes(); -} - -void FeedFilterManager::AddPresets() { - foreach_(preset, presets) { - if (!preset->is_default) continue; - AddFilter(preset->filter.action, preset->filter.match, preset->filter.option, - preset->filter.enabled, preset->filter.name); - foreach_(condition, preset->filter.conditions) { - filters.back().AddCondition(condition->element, - condition->op, - condition->value); - } - } -} - -void FeedFilterManager::AddFilter(int action, int match, int option, bool enabled, const wstring& name) { - filters.resize(filters.size() + 1); - filters.back().action = action; - filters.back().enabled = enabled; - filters.back().match = match; - filters.back().name = name; - filters.back().option = option; -} - -void FeedFilterManager::Cleanup() { - foreach_(filter, filters) { - foreach_(id, filter->anime_ids) { - if (!AnimeDatabase.FindItem(*id)) { - if (filter->anime_ids.size() > 1) { - id = filter->anime_ids.erase(id) - 1; - continue; - } else { - filter = filters.erase(filter) - 1; - break; - } - } - } - } -} - -void FeedFilterManager::Filter(Feed& feed, bool preferences) { - if (!Settings.RSS.Torrent.Filters.global_enabled) - return; - - foreach_(item, feed.items) { - foreach_(filter, filters) { - if (preferences != (filter->action == FEED_FILTER_ACTION_PREFER)) - continue; - filter->Filter(feed, *item, true); - } - } -} - -void FeedFilterManager::FilterArchived(Feed& feed) { - foreach_(item, feed.items) { - if (!item->IsDiscarded()) { - bool found = Aggregator.SearchArchive(item->title); - if (found) { - item->state = FEEDITEM_DISCARDED_NORMAL; - -#ifdef _DEBUG - wstring filter_text = L"!FILTER :: Archived"; - item->description = filter_text + L" -- " + item->description; -#endif - } - } - } -} - -bool FeedFilterManager::IsItemDownloadAvailable(Feed& feed) { - foreach_c_(item, feed.items) - if (item->state == FEEDITEM_SELECTED) - return true; - - return false; -} - -void FeedFilterManager::MarkNewEpisodes(Feed& feed) { - foreach_(item, feed.items) { - auto anime_item = AnimeDatabase.FindItem(item->episode_data.anime_id); - if (anime_item) { - int number = GetEpisodeHigh(item->episode_data.number); - if (number > anime_item->GetMyLastWatchedEpisode()) - item->episode_data.new_episode = true; - } - } -} - -// ============================================================================= - -void FeedFilterManager::InitializePresets() { - #define ADD_PRESET(action_, match_, is_default_, option_, name_, description_) \ - presets.resize(presets.size() + 1); \ - presets.back().description = description_; \ - presets.back().is_default = is_default_; \ - presets.back().filter.action = action_; \ - presets.back().filter.enabled = true; \ - presets.back().filter.match = match_; \ - presets.back().filter.option = option_; \ - presets.back().filter.name = name_; - #define ADD_CONDITION(e, o, v) \ - presets.back().filter.AddCondition(e, o, v); - - /* Preset filters */ - - // Custom - ADD_PRESET(FEED_FILTER_ACTION_DISCARD, FEED_FILTER_MATCH_ALL, false, FEED_FILTER_OPTION_DEFAULT, - L"(Custom)", - L"Lets you create a custom filter from scratch"); - - // Fansub group - ADD_PRESET(FEED_FILTER_ACTION_PREFER, FEED_FILTER_MATCH_ALL, false, FEED_FILTER_OPTION_DEFAULT, - L"[Fansub] Anime", - L"Lets you choose a fansub group for one or more anime"); - ADD_CONDITION(FEED_FILTER_ELEMENT_EPISODE_GROUP, FEED_FILTER_OPERATOR_EQUALS, L"TaigaSubs (change this)"); - - // Discard bad video keywords - ADD_PRESET(FEED_FILTER_ACTION_DISCARD, FEED_FILTER_MATCH_ANY, false, FEED_FILTER_OPTION_DEFAULT, - L"Discard bad video keywords", - L"Discards everything that is AVI, DIVX, LQ, RMVB, SD, WMV or XVID"); - ADD_CONDITION(FEED_FILTER_ELEMENT_EPISODE_VIDEO_TYPE, FEED_FILTER_OPERATOR_CONTAINS, L"AVI"); - ADD_CONDITION(FEED_FILTER_ELEMENT_EPISODE_VIDEO_TYPE, FEED_FILTER_OPERATOR_CONTAINS, L"DIVX"); - ADD_CONDITION(FEED_FILTER_ELEMENT_EPISODE_VIDEO_TYPE, FEED_FILTER_OPERATOR_CONTAINS, L"LQ"); - ADD_CONDITION(FEED_FILTER_ELEMENT_EPISODE_VIDEO_TYPE, FEED_FILTER_OPERATOR_CONTAINS, L"RMVB"); - ADD_CONDITION(FEED_FILTER_ELEMENT_EPISODE_VIDEO_TYPE, FEED_FILTER_OPERATOR_CONTAINS, L"SD"); - ADD_CONDITION(FEED_FILTER_ELEMENT_EPISODE_VIDEO_TYPE, FEED_FILTER_OPERATOR_CONTAINS, L"WMV"); - ADD_CONDITION(FEED_FILTER_ELEMENT_EPISODE_VIDEO_TYPE, FEED_FILTER_OPERATOR_CONTAINS, L"XVID"); - - // Prefer new versions - ADD_PRESET(FEED_FILTER_ACTION_PREFER, FEED_FILTER_MATCH_ANY, false, FEED_FILTER_OPTION_DEFAULT, - L"Prefer new versions", - L"Prefers v2 files and above when there are earlier releases of the same episode as well"); - ADD_CONDITION(FEED_FILTER_ELEMENT_EPISODE_VERSION, FEED_FILTER_OPERATOR_ISGREATERTHAN, L"1"); - - /* Default filters */ - - // Select currently watching - ADD_PRESET(FEED_FILTER_ACTION_SELECT, FEED_FILTER_MATCH_ANY, true, FEED_FILTER_OPTION_DEFAULT, - L"Select currently watching", - L"Selects files that belong to anime that you're currently watching"); - ADD_CONDITION(FEED_FILTER_ELEMENT_USER_STATUS, FEED_FILTER_OPERATOR_EQUALS, ToWstr(mal::MYSTATUS_WATCHING)); - - // Discard unknown titles - ADD_PRESET(FEED_FILTER_ACTION_DISCARD, FEED_FILTER_MATCH_ANY, true, FEED_FILTER_OPTION_DEACTIVATE, - L"Discard unknown titles", - L"Discards files that do not belong to any anime in your list"); - ADD_CONDITION(FEED_FILTER_ELEMENT_META_ID, FEED_FILTER_OPERATOR_EQUALS, L""); - - // Discard watched and available episodes - ADD_PRESET(FEED_FILTER_ACTION_DISCARD, FEED_FILTER_MATCH_ANY, true, FEED_FILTER_OPTION_DEFAULT, - L"Discard watched and available episodes", - L"Discards episodes you've already watched or downloaded"); - ADD_CONDITION(FEED_FILTER_ELEMENT_EPISODE_NUMBER, FEED_FILTER_OPERATOR_ISLESSTHANOREQUALTO, L"%watched%"); - ADD_CONDITION(FEED_FILTER_ELEMENT_LOCAL_EPISODE_AVAILABLE, FEED_FILTER_OPERATOR_EQUALS, L"True"); - - // Prefer high-resolution files - ADD_PRESET(FEED_FILTER_ACTION_PREFER, FEED_FILTER_MATCH_ANY, true, FEED_FILTER_OPTION_DEFAULT, - L"Prefer high-resolution files", - L"Prefers 720p files when there are other files of the same episode as well"); - ADD_CONDITION(FEED_FILTER_ELEMENT_EPISODE_VIDEO_RESOLUTION, FEED_FILTER_OPERATOR_EQUALS, L"720p"); - - #undef ADD_CONDITION - #undef ADD_PRESET -} - -void FeedFilterManager::InitializeShortcodes() { - action_shortcodes_[FEED_FILTER_ACTION_DISCARD] = L"discard"; - action_shortcodes_[FEED_FILTER_ACTION_SELECT] = L"select"; - action_shortcodes_[FEED_FILTER_ACTION_PREFER] = L"prefer"; - - element_shortcodes_[FEED_FILTER_ELEMENT_META_ID] = L"meta_id"; - element_shortcodes_[FEED_FILTER_ELEMENT_META_STATUS] = L"meta_status"; - element_shortcodes_[FEED_FILTER_ELEMENT_META_TYPE] = L"meta_type"; - element_shortcodes_[FEED_FILTER_ELEMENT_META_EPISODES] = L"meta_episodes"; - element_shortcodes_[FEED_FILTER_ELEMENT_META_DATE_START] = L"meta_date_start"; - element_shortcodes_[FEED_FILTER_ELEMENT_META_DATE_END] = L"meta_date_end"; - element_shortcodes_[FEED_FILTER_ELEMENT_USER_STATUS] = L"user_status"; - element_shortcodes_[FEED_FILTER_ELEMENT_LOCAL_EPISODE_AVAILABLE] = L"local_episode_available"; - element_shortcodes_[FEED_FILTER_ELEMENT_EPISODE_TITLE] = L"episode_title"; - element_shortcodes_[FEED_FILTER_ELEMENT_EPISODE_NUMBER] = L"episode_number"; - element_shortcodes_[FEED_FILTER_ELEMENT_EPISODE_VERSION] = L"episode_version"; - element_shortcodes_[FEED_FILTER_ELEMENT_EPISODE_GROUP] = L"episode_group"; - element_shortcodes_[FEED_FILTER_ELEMENT_EPISODE_VIDEO_RESOLUTION] = L"episode_video_resolution"; - element_shortcodes_[FEED_FILTER_ELEMENT_EPISODE_VIDEO_TYPE] = L"episode_video_type"; - element_shortcodes_[FEED_FILTER_ELEMENT_FILE_TITLE] = L"file_title"; - element_shortcodes_[FEED_FILTER_ELEMENT_FILE_CATEGORY] = L"file_category"; - element_shortcodes_[FEED_FILTER_ELEMENT_FILE_DESCRIPTION] = L"file_description"; - element_shortcodes_[FEED_FILTER_ELEMENT_FILE_LINK] = L"file_link"; - - match_shortcodes_[FEED_FILTER_MATCH_ALL] = L"all"; - match_shortcodes_[FEED_FILTER_MATCH_ANY] = L"any"; - - operator_shortcodes_[FEED_FILTER_OPERATOR_EQUALS] = L"equals"; - operator_shortcodes_[FEED_FILTER_OPERATOR_NOTEQUALS] = L"notequals"; - operator_shortcodes_[FEED_FILTER_OPERATOR_ISGREATERTHAN] = L"gt"; - operator_shortcodes_[FEED_FILTER_OPERATOR_ISGREATERTHANOREQUALTO] = L"ge"; - operator_shortcodes_[FEED_FILTER_OPERATOR_ISLESSTHAN] = L"lt"; - operator_shortcodes_[FEED_FILTER_OPERATOR_ISLESSTHANOREQUALTO] = L"le"; - operator_shortcodes_[FEED_FILTER_OPERATOR_BEGINSWITH] = L"beginswith"; - operator_shortcodes_[FEED_FILTER_OPERATOR_ENDSWITH] = L"endswith"; - operator_shortcodes_[FEED_FILTER_OPERATOR_CONTAINS] = L"contains"; - operator_shortcodes_[FEED_FILTER_OPERATOR_NOTCONTAINS] = L"notcontains"; - - option_shortcodes_[FEED_FILTER_OPTION_DEFAULT] = L"default"; - option_shortcodes_[FEED_FILTER_OPTION_DEACTIVATE] = L"deactivate"; - option_shortcodes_[FEED_FILTER_OPTION_HIDE] = L"hide"; -} - -wstring FeedFilterManager::CreateNameFromConditions(const FeedFilter& filter) { - // TODO - return L"New Filter"; -} - -wstring FeedFilterManager::TranslateCondition(const FeedFilterCondition& condition) { - return TranslateElement(condition.element) + L" " + - TranslateOperator(condition.op) + L" \"" + - TranslateValue(condition) + L"\""; -} - -wstring FeedFilterManager::TranslateConditions(const FeedFilter& filter, size_t index) { - wstring str; - - size_t max_index = (filter.match == FEED_FILTER_MATCH_ALL) ? - filter.conditions.size() : index + 1; - - for (size_t i = index; i < max_index; i++) { - if (i > index) str += L" & "; - str += TranslateCondition(filter.conditions[i]); - } - - return str; -} - -wstring FeedFilterManager::TranslateElement(int element) { - switch (element) { - case FEED_FILTER_ELEMENT_FILE_TITLE: - return L"File name"; - case FEED_FILTER_ELEMENT_FILE_CATEGORY: - return L"File category"; - case FEED_FILTER_ELEMENT_FILE_DESCRIPTION: - return L"File description"; - case FEED_FILTER_ELEMENT_FILE_LINK: - return L"File link"; - case FEED_FILTER_ELEMENT_META_ID: - return L"Anime ID"; - case FEED_FILTER_ELEMENT_EPISODE_TITLE: - return L"Episode title"; - case FEED_FILTER_ELEMENT_META_DATE_START: - return L"Anime date started"; - case FEED_FILTER_ELEMENT_META_DATE_END: - return L"Anime date ended"; - case FEED_FILTER_ELEMENT_META_EPISODES: - return L"Anime episode count"; - case FEED_FILTER_ELEMENT_META_STATUS: - return L"Anime airing status"; - case FEED_FILTER_ELEMENT_META_TYPE: - return L"Anime type"; - case FEED_FILTER_ELEMENT_USER_STATUS: - return L"Anime watching status"; - case FEED_FILTER_ELEMENT_EPISODE_NUMBER: - return L"Episode number"; - case FEED_FILTER_ELEMENT_EPISODE_VERSION: - return L"Episode version"; - case FEED_FILTER_ELEMENT_LOCAL_EPISODE_AVAILABLE: - return L"Episode availability"; - case FEED_FILTER_ELEMENT_EPISODE_GROUP: - return L"Episode fansub group"; - case FEED_FILTER_ELEMENT_EPISODE_VIDEO_RESOLUTION: - return L"Episode video resolution"; - case FEED_FILTER_ELEMENT_EPISODE_VIDEO_TYPE: - return L"Episode video type"; - default: - return L"?"; - } -} - -wstring FeedFilterManager::TranslateOperator(int op) { - switch (op) { - case FEED_FILTER_OPERATOR_EQUALS: - return L"is"; - case FEED_FILTER_OPERATOR_NOTEQUALS: - return L"is not"; - case FEED_FILTER_OPERATOR_ISGREATERTHAN: - return L"is greater than"; - case FEED_FILTER_OPERATOR_ISGREATERTHANOREQUALTO: - return L"is greater than or equal to"; - case FEED_FILTER_OPERATOR_ISLESSTHAN: - return L"is less than"; - case FEED_FILTER_OPERATOR_ISLESSTHANOREQUALTO: - return L"is less than or equal to"; - case FEED_FILTER_OPERATOR_BEGINSWITH: - return L"begins with"; - case FEED_FILTER_OPERATOR_ENDSWITH: - return L"ends with"; - case FEED_FILTER_OPERATOR_CONTAINS: - return L"contains"; - case FEED_FILTER_OPERATOR_NOTCONTAINS: - return L"does not contain"; - default: - return L"?"; - } -} - -wstring FeedFilterManager::TranslateValue(const FeedFilterCondition& condition) { - switch (condition.element) { - case FEED_FILTER_ELEMENT_META_ID: { - if (condition.value.empty()) { - return L"(?)"; - } else { - auto anime_item = AnimeDatabase.FindItem(ToInt(condition.value)); - if (anime_item) { - return condition.value + L" (" + anime_item->GetTitle() + L")"; - } else { - return condition.value + L" (?)"; - } - } - } - case FEED_FILTER_ELEMENT_USER_STATUS: - return mal::TranslateMyStatus(ToInt(condition.value), false); - case FEED_FILTER_ELEMENT_META_STATUS: - return mal::TranslateStatus(ToInt(condition.value)); - case FEED_FILTER_ELEMENT_META_TYPE: - return mal::TranslateType(ToInt(condition.value)); - default: - return condition.value; - } -} - -wstring FeedFilterManager::TranslateMatching(int match) { - switch (match) { - case FEED_FILTER_MATCH_ALL: - return L"All conditions"; - case FEED_FILTER_MATCH_ANY: - return L"Any condition"; - default: - return L"?"; - } -} - -wstring FeedFilterManager::TranslateAction(int action) { - switch (action) { - case FEED_FILTER_ACTION_DISCARD: - return L"Discard matched items"; - case FEED_FILTER_ACTION_SELECT: - return L"Select matched items"; - case FEED_FILTER_ACTION_PREFER: - return L"Prefer matched items to similar ones"; - default: - return L"?"; - } -} - - -wstring FeedFilterManager::TranslateOption(int option) { - switch (option) { - case FEED_FILTER_OPTION_DEFAULT: - return L"Default"; - case FEED_FILTER_OPTION_DEACTIVATE: - return L"Deactivate discarded items"; - case FEED_FILTER_OPTION_HIDE: - return L"Hide discarded items"; - default: - return L"?"; - } -} - -wstring FeedFilterManager::GetShortcodeFromIndex(FeedFilterShortcodeType type, - int index) { - switch (type) { - case FEED_FILTER_SHORTCODE_ACTION: - return action_shortcodes_[index]; - case FEED_FILTER_SHORTCODE_ELEMENT: - return element_shortcodes_[index]; - case FEED_FILTER_SHORTCODE_MATCH: - return match_shortcodes_[index]; - case FEED_FILTER_SHORTCODE_OPERATOR: - return operator_shortcodes_[index]; - case FEED_FILTER_SHORTCODE_OPTION: - return option_shortcodes_[index]; - } - - return wstring(); -} - -int FeedFilterManager::GetIndexFromShortcode(FeedFilterShortcodeType type, - const wstring& shortcode) { - std::map* shortcodes = nullptr; - switch (type) { - case FEED_FILTER_SHORTCODE_ACTION: - shortcodes = &action_shortcodes_; - break; - case FEED_FILTER_SHORTCODE_ELEMENT: - shortcodes = &element_shortcodes_; - break; - case FEED_FILTER_SHORTCODE_MATCH: - shortcodes = &match_shortcodes_; - break; - case FEED_FILTER_SHORTCODE_OPERATOR: - shortcodes = &operator_shortcodes_; - break; - case FEED_FILTER_SHORTCODE_OPTION: - shortcodes = &option_shortcodes_; - break; - } - - foreach_(it, *shortcodes) - if (IsEqual(it->second, shortcode)) - return it->first; - - LOG(LevelDebug, L"Shortcode: \"" + shortcode + - L"\" for type \"" + ToWstr(type) + L"\" is not found."); - - return -1; -} \ No newline at end of file diff --git a/history.cpp b/history.cpp deleted file mode 100644 index d67b4530d..000000000 --- a/history.cpp +++ /dev/null @@ -1,487 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "history.h" - -#include "anime_db.h" -#include "announce.h" -#include "common.h" -#include "http.h" -#include "logger.h" -#include "myanimelist.h" -#include "resource.h" -#include "settings.h" -#include "string.h" -#include "taiga.h" -#include "xml.h" - -#include "dlg/dlg_anime_info.h" -#include "dlg/dlg_anime_list.h" -#include "dlg/dlg_history.h" -#include "dlg/dlg_main.h" - -#include "win32/win_taskdialog.h" - -class ConfirmationQueue ConfirmationQueue; -class History History; - -// ============================================================================= - -EventItem::EventItem() - : anime_id(anime::ID_UNKNOWN), - enabled(true), - mode(0) { -} - -EventQueue::EventQueue() - : index(0), - history(nullptr), - updating(false) { -} - -void EventQueue::Add(EventItem& item, bool save) { - auto anime = AnimeDatabase.FindItem(item.anime_id); - - // Add to user list - if (anime && !anime->IsInList()) - if (item.mode != HTTP_MAL_AnimeDelete) - anime->AddtoUserList(); - - // Validate values - if (anime && anime->IsInList()) { - if (item.episode) - if (anime->GetMyLastWatchedEpisode() == *item.episode || *item.episode < 0) - item.episode.Reset(); - if (item.score) - if (anime->GetMyScore() == *item.score || *item.score < 0 || *item.score > 10) - item.score.Reset(); - if (item.status) - if (anime->GetMyStatus() == *item.status || *item.status < 1 || *item.status == 5 || *item.status > 6) - item.status.Reset(); - if (item.enable_rewatching) - if (anime->GetMyRewatching() == *item.enable_rewatching) - item.enable_rewatching.Reset(); - if (item.tags) - if (anime->GetMyTags() == *item.tags) - item.tags.Reset(); - if (item.date_start) - if (anime->GetMyDate(anime::DATE_START) == mal::TranslateDateFromApi(*item.date_start)) - item.date_start.Reset(); - if (item.date_finish) - if (anime->GetMyDate(anime::DATE_END) == mal::TranslateDateFromApi(*item.date_finish)) - item.date_finish.Reset(); - } - switch (item.mode) { - case HTTP_MAL_AnimeUpdate: - if (!item.episode && - !item.score && - !item.status && - !item.enable_rewatching && - !item.tags && - !item.date_start && - !item.date_finish) return; - break; - } - - // Edit previous item with the same ID... - bool add_new_item = true; - if (!History.queue.updating) { - for (auto it = items.rbegin(); it != items.rend(); ++it) { - if (it->anime_id == item.anime_id && it->enabled) { - if (it->mode != HTTP_MAL_AnimeAdd && it->mode != HTTP_MAL_AnimeDelete) { - if (!item.episode || (!it->episode && it == items.rbegin())) { - if (item.episode) it->episode = *item.episode; - if (item.score) it->score = *item.score; - if (item.status) it->status = *item.status; - if (item.enable_rewatching) it->enable_rewatching = *item.enable_rewatching; - if (item.tags) it->tags = *item.tags; - if (item.date_start) it->date_start = *item.date_start; - if (item.date_finish) it->date_finish = *item.date_finish; - add_new_item = false; - } - if (!add_new_item) { - it->mode = HTTP_MAL_AnimeUpdate; - it->time = (wstring)GetDate() + L" " + GetTime(); - } - break; - } - } - } - } - // ...or add a new one - if (add_new_item) { - if (item.time.empty()) item.time = (wstring)GetDate() + L" " + GetTime(); - items.push_back(item); - } - - if (anime && save) { - // Save - history->Save(); - - // Announce - if (Taiga.logged_in && item.episode) { - anime::Episode episode; - episode.anime_id = anime->GetId(); - episode.number = ToWstr(*item.episode); - Taiga.play_status = PLAYSTATUS_UPDATED; - Announcer.Do(ANNOUNCE_TO_HTTP | ANNOUNCE_TO_TWITTER, &episode); - } - - // Check new episode - if (item.episode) { - anime->SetNewEpisodePath(L""); - anime->CheckEpisodes(0); - } - - // Refresh history - MainDialog.treeview.RefreshHistoryCounter(); - HistoryDialog.RefreshList(); - - // Refresh anime window - if (item.mode == HTTP_MAL_AnimeAdd || item.mode == HTTP_MAL_AnimeDelete || - item.status || item.enable_rewatching) { - AnimeListDialog.RefreshList(); - AnimeListDialog.RefreshTabs(); - } else { - AnimeListDialog.RefreshListItem(item.anime_id); - } - - // Refresh now playing - NowPlayingDialog.Refresh(false, false, false); - - // Change status - if (!Taiga.logged_in) { - MainDialog.ChangeStatus(L"\"" + anime->GetTitle() + - L"\" is queued for update."); - } - - // Update - Check(); - } -} - -void EventQueue::Check(bool automatic) { - // Check - if (items.empty()) { - return; - } - if (!items[index].enabled) { - LOG(LevelDebug, L"Item is disabled, removing..."); - Remove(index); - Check(); - return; - } - if (!Taiga.logged_in) { - items[index].reason = L"Not logged in"; - return; - } - if (automatic && !Settings.Program.General.enable_sync) { - items[index].reason = L"Synchronization is disabled"; - return; - } - - // Compare ID with anime list - auto anime_item = AnimeDatabase.FindItem(items[index].anime_id); - if (!anime_item) { - LOG(LevelWarning, L"Item not found in list, removing... ID: " + - ToWstr(items[index].anime_id)); - Remove(index); - Check(); - return; - } - - // Update - History.queue.updating = true; - MainDialog.ChangeStatus(L"Updating list..."); - mal::AnimeValues* anime_values = static_cast(&items[index]); - mal::Update(*anime_values, items[index].anime_id, items[index].mode); -} - -void EventQueue::Clear(bool save) { - items.clear(); - index = 0; - - MainDialog.treeview.RefreshHistoryCounter(); - NowPlayingDialog.Refresh(false, false, false); - - if (save) history->Save(); -} - -EventItem* EventQueue::FindItem(int anime_id, int search_mode) { - for (auto it = items.rbegin(); it != items.rend(); ++it) { - if (it->anime_id == anime_id && it->enabled) { - switch (search_mode) { - // Date - case EVENT_SEARCH_DATE_START: - if (it->date_start) return &(*it); - break; - case EVENT_SEARCH_DATE_END: - if (it->date_finish) return &(*it); - break; - // Episode - case EVENT_SEARCH_EPISODE: - if (it->episode) return &(*it); - break; - // Re-watching - case EVENT_SEARCH_REWATCH: - if (it->enable_rewatching) return &(*it); - break; - // Score - case EVENT_SEARCH_SCORE: - if (it->score) return &(*it); - break; - // Status - case EVENT_SEARCH_STATUS: - if (it->status) return &(*it); - break; - // Tags - case EVENT_SEARCH_TAGS: - if (it->tags) return &(*it); - break; - // Default - default: - return &(*it); - } - } - } - return nullptr; -} - -EventItem* EventQueue::GetCurrentItem() { - if (!items.empty()) { - return &items.at(index); - } - return nullptr; -} - -int EventQueue::GetItemCount() { - int count = 0; - for (auto it = items.begin(); it != items.end(); ++it) - if (it->enabled) count++; - return count; -} - -void EventQueue::Remove(int index, bool save, bool refresh, bool to_history) { - if (index == -1) index = this->index; - - if (index < static_cast(items.size())) { - auto event_item = items.begin() + index; - - if (to_history && event_item->episode) { - history->items.push_back(*event_item); - if (history->limit > 0 && static_cast(history->items.size()) > history->limit) { - history->items.erase(history->items.begin()); - } - } - - items.erase(event_item); - - if (refresh) { - MainDialog.treeview.RefreshHistoryCounter(); - NowPlayingDialog.Refresh(false, false, false); - HistoryDialog.RefreshList(); - } - } - - if (save) history->Save(); -} - -void EventQueue::RemoveDisabled(bool save, bool refresh) { - bool needs_refresh = false; - - for (size_t i = 0; i < items.size(); i++) { - if (!items.at(i).enabled) { - items.erase(items.begin() + i); - needs_refresh = true; - i--; - } - } - - if (refresh && needs_refresh) { - MainDialog.treeview.RefreshHistoryCounter(); - NowPlayingDialog.Refresh(false, false, false); - HistoryDialog.RefreshList(); - } - - if (save) history->Save(); -} - -// ============================================================================= - -History::History() - : limit(0) { // Limit of history items - queue.history = this; -} - -bool History::Load() { - // Initialize - wstring file = Taiga.GetDataPath() + L"user\\" + Settings.Account.MAL.user + L"\\history.xml"; - - // Load XML file - xml_document doc; - xml_parse_result result = doc.load_file(file.c_str()); - - // Items - items.clear(); - xml_node node_items = doc.child(L"history").child(L"items"); - for (xml_node item = node_items.child(L"item"); item; item = item.next_sibling(L"item")) { - EventItem event_item; - event_item.anime_id = item.attribute(L"anime_id").as_int(anime::ID_NOTINLIST); - event_item.episode = item.attribute(L"episode").as_int(); - event_item.time = item.attribute(L"time").value(); - items.push_back(event_item); - } - // Queue events - queue.items.clear(); - xml_node node_queue = doc.child(L"history").child(L"queue"); - for (xml_node item = node_queue.child(L"item"); item; item = item.next_sibling(L"item")) { - EventItem event_item; - event_item.anime_id = item.attribute(L"anime_id").as_int(anime::ID_NOTINLIST); - event_item.mode = item.attribute(L"mode").as_int(); - event_item.time = item.attribute(L"time").value(); - #define READ_ATTRIBUTE_INT(x, y) \ - if (!item.attribute(y).empty()) x = item.attribute(y).as_int(); - #define READ_ATTRIBUTE_STR(x, y) \ - if (!item.attribute(y).empty()) x = item.attribute(y).as_string(); - READ_ATTRIBUTE_INT(event_item.episode, L"episode"); - READ_ATTRIBUTE_INT(event_item.score, L"score"); - READ_ATTRIBUTE_INT(event_item.status, L"status"); - READ_ATTRIBUTE_INT(event_item.enable_rewatching, L"enable_rewatching"); - READ_ATTRIBUTE_STR(event_item.tags, L"tags"); - READ_ATTRIBUTE_STR(event_item.date_start, L"date_start"); - READ_ATTRIBUTE_STR(event_item.date_finish, L"date_finish"); - #undef READ_ATTRIBUTE_STR - #undef READ_ATTRIBUTE_INT - queue.Add(event_item, false); - } - - return result.status == status_ok; -} - -bool History::Save() { - // Initialize - wstring file = Taiga.GetDataPath() + L"user\\" + Settings.Account.MAL.user + L"\\history.xml"; - xml_document doc; - xml_node node_history = doc.append_child(L"history"); - - // Write event items - xml_node node_items = node_history.append_child(L"items"); - for (auto j = items.begin(); j != items.end(); ++j) { - xml_node node_item = node_items.append_child(L"item"); - node_item.append_attribute(L"anime_id") = j->anime_id; - node_item.append_attribute(L"episode") = *j->episode; - node_item.append_attribute(L"time") = j->time.c_str(); - } - // Write event queue - xml_node node_queue = node_history.append_child(L"queue"); - for (auto j = queue.items.begin(); j != queue.items.end(); ++j) { - xml_node node_item = node_queue.append_child(L"item"); - #define APPEND_ATTRIBUTE_INT(x, y) \ - if (y) node_item.append_attribute(x) = *y; - #define APPEND_ATTRIBUTE_STR(x, y) \ - if (y) node_item.append_attribute(x) = (*y).c_str(); - node_item.append_attribute(L"anime_id") = j->anime_id; - node_item.append_attribute(L"mode") = j->mode; - node_item.append_attribute(L"time") = j->time.c_str(); - APPEND_ATTRIBUTE_INT(L"episode", j->episode); - APPEND_ATTRIBUTE_INT(L"score", j->score); - APPEND_ATTRIBUTE_INT(L"status", j->status); - APPEND_ATTRIBUTE_INT(L"enable_rewatching", j->enable_rewatching); - APPEND_ATTRIBUTE_STR(L"tags", j->tags); - APPEND_ATTRIBUTE_STR(L"date_start", j->date_start); - APPEND_ATTRIBUTE_STR(L"date_finish", j->date_finish); - #undef APPEND_ATTRIBUTE_STR - #undef APPEND_ATTRIBUTE_INT - } - - // Save file - return doc.save_file(file.c_str(), L"\x09", format_default | format_write_bom); -} - -// ============================================================================= - -ConfirmationQueue::ConfirmationQueue() - : in_process(false) { -} - -void ConfirmationQueue::Add(const anime::Episode& episode) { - queue_.push(episode); -} - -int AskForConfirmation(anime::Episode& episode) { - auto anime_item = AnimeDatabase.FindItem(episode.anime_id); - - // Set up dialog - win32::TaskDialog dlg; - wstring title = L"Anime title: " + anime_item->GetTitle(); - dlg.SetWindowTitle(APP_TITLE); - dlg.SetMainIcon(TD_ICON_INFORMATION); - dlg.SetMainInstruction(L"Do you want to update your anime list?"); - dlg.SetContent(title.c_str()); - dlg.SetVerificationText(L"Don't ask again, update automatically"); - dlg.UseCommandLinks(true); - - // Get episode number - int number = GetEpisodeHigh(episode.number); - if (number == 0) number = 1; - if (anime_item->GetEpisodeCount() == 1) episode.number = L"1"; - - // Add buttons - if (anime_item->GetMyStatus() != mal::MYSTATUS_NOTINLIST) { - if (anime_item->GetEpisodeCount() == number) { // Completed - dlg.AddButton(L"Update and move\n" - L"Update and set as completed", IDCANCEL); - } else if (anime_item->GetMyStatus() != mal::MYSTATUS_WATCHING) { // Watching - dlg.AddButton(L"Update and move\n" - L"Update and set as watching", IDCANCEL); - } - } - wstring button = L"Update\n" - L"Update episode number from " + - ToWstr(anime_item->GetMyLastWatchedEpisode()) + - L" to " + ToWstr(number); - dlg.AddButton(button.c_str(), IDYES); - dlg.AddButton(L"Cancel\n" - L"Don't update anything", IDNO); - - // Show dialog - dlg.Show(g_hMain); - if (dlg.GetVerificationCheck()) - Settings.Account.Update.ask_to_confirm = FALSE; - return dlg.GetSelectedButtonID(); -} - -void ConfirmationQueue::Process() { - if (in_process) return; - in_process = true; - - while (!queue_.empty()) { - anime::Episode& episode = queue_.front(); - int choice = AskForConfirmation(episode); - if (choice != IDNO) { - bool change_status = (choice == IDCANCEL); - auto anime_item = AnimeDatabase.FindItem(episode.anime_id); - anime_item->AddToQueue(episode, change_status); - } - queue_.pop(); - } - - in_process = false; -} \ No newline at end of file diff --git a/http.cpp b/http.cpp deleted file mode 100644 index 6c62e57f1..000000000 --- a/http.cpp +++ /dev/null @@ -1,702 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "http.h" - -#include "anime_db.h" -#include "announce.h" -#include "common.h" -#include "feed.h" -#include "history.h" -#include "logger.h" -#include "myanimelist.h" -#include "resource.h" -#include "settings.h" -#include "stats.h" -#include "string.h" -#include "taiga.h" -#include "theme.h" -#include "version.h" - -#include "dlg/dlg_anime_info.h" -#include "dlg/dlg_anime_info_page.h" -#include "dlg/dlg_anime_list.h" -#include "dlg/dlg_history.h" -#include "dlg/dlg_input.h" -#include "dlg/dlg_main.h" -#include "dlg/dlg_search.h" -#include "dlg/dlg_season.h" -#include "dlg/dlg_settings.h" -#include "dlg/dlg_torrent.h" -#include "dlg/dlg_update.h" - -#include "win32/win_taskbar.h" -#include "win32/win_taskdialog.h" - -HttpClients Clients; - -// ============================================================================= - -HttpClient::HttpClient() { - SetAutoRedirect(FALSE); - SetUserAgent(APP_NAME L"/" + ToWstr(VERSION_MAJOR) + L"." + ToWstr(VERSION_MINOR)); -} - -void HttpClient::OnInitialize() { - switch (GetClientMode()) { - case HTTP_MAL_Login: - case HTTP_MAL_RefreshList: - case HTTP_MAL_AnimeAdd: - case HTTP_MAL_AnimeAskToDiscuss: - case HTTP_MAL_AnimeDelete: - case HTTP_MAL_AnimeDetails: - case HTTP_MAL_AnimeUpdate: - case HTTP_MAL_SearchAnime: - case HTTP_MAL_Image: - case HTTP_MAL_UserImage: - // - // wow such - // no spoof security - // - // so obscure hyper txt plz - // - SetUserAgent( - L"api-" - L"taiga-" - L"32864c09" - L"ef538453" - L"b4d81107" - L"34ee355b"); - break; - } -} - -BOOL HttpClient::OnError(DWORD dwError) { - wstring error_text = L"HTTP error #" + ToWstr(dwError) + L": " + - FormatError(dwError, L"winhttp.dll"); - TrimRight(error_text, L"\r\n"); - - LOG(LevelError, error_text); - LOG(LevelError, L"Client mode: " + ToWstr(GetClientMode())); - - Stats.connections_failed++; - - switch (GetClientMode()) { - case HTTP_MAL_Login: - case HTTP_MAL_RefreshList: - MainDialog.ChangeStatus(error_text); - MainDialog.EnableInput(true); - break; - case HTTP_MAL_AnimeAskToDiscuss: - case HTTP_MAL_AnimeDetails: - case HTTP_MAL_Image: - case HTTP_MAL_SearchAnime: - case HTTP_MAL_UserImage: - case HTTP_Feed_DownloadIcon: -#ifdef _DEBUG - MainDialog.ChangeStatus(error_text); -#endif - break; - case HTTP_Feed_Check: - case HTTP_Feed_CheckAuto: - case HTTP_Feed_Download: - case HTTP_Feed_DownloadAll: - MainDialog.ChangeStatus(error_text); - TorrentDialog.EnableInput(); - break; - case HTTP_UpdateCheck: - case HTTP_UpdateDownload: - MessageBox(UpdateDialog.GetWindowHandle(), - error_text.c_str(), L"Update", MB_ICONERROR | MB_OK); - UpdateDialog.PostMessage(WM_CLOSE); - break; - default: - History.queue.updating = false; - MainDialog.ChangeStatus(error_text); - MainDialog.EnableInput(true); - break; - } - - TaskbarList.SetProgressState(TBPF_NOPROGRESS); - return 0; -} - -BOOL HttpClient::OnSendRequestComplete() { -#ifdef _DEBUG - MainDialog.ChangeStatus(L"Connecting..."); -#endif - - return 0; -} - -BOOL HttpClient::OnHeadersAvailable(win32::http_header_t& headers) { - switch (GetClientMode()) { - case HTTP_Silent: - break; - case HTTP_UpdateCheck: - case HTTP_UpdateDownload: - if (m_dwTotal > 0) { - UpdateDialog.progressbar.SetMarquee(false); - UpdateDialog.progressbar.SetRange(0, m_dwTotal); - } else { - UpdateDialog.progressbar.SetMarquee(true); - } - if (GetClientMode() == HTTP_UpdateDownload) { - UpdateDialog.SetDlgItemText(IDC_STATIC_UPDATE_PROGRESS, L"Downloading updates..."); - } - break; - default: - TaskbarList.SetProgressState(m_dwTotal > 0 ? TBPF_NORMAL : TBPF_INDETERMINATE); - break; - } - - return 0; -} - -BOOL HttpClient::OnRedirect(wstring address) { - switch (GetClientMode()) { - case HTTP_UpdateDownload: { - wstring file = address.substr(address.find_last_of(L"/") + 1); - m_File = GetPathOnly(m_File) + file; - Taiga.Updater.SetDownloadPath(m_File); - break; - } - } - -#ifdef _DEBUG - MainDialog.ChangeStatus(L"Redirecting... (" + address + L")"); -#endif - - return 0; -} - -BOOL HttpClient::OnReadData() { - wstring status; - - switch (GetClientMode()) { - case HTTP_MAL_RefreshList: - status = L"Downloading anime list..."; - break; - case HTTP_MAL_Login: - status = L"Reading account information..."; - break; - case HTTP_MAL_AnimeAdd: - case HTTP_MAL_AnimeUpdate: - status = L"Updating list..."; - break; - case HTTP_MAL_AnimeAskToDiscuss: - case HTTP_Feed_DownloadIcon: - case HTTP_MAL_AnimeDetails: - case HTTP_MAL_SearchAnime: - case HTTP_MAL_Image: - case HTTP_MAL_UserImage: - case HTTP_Silent: - return 0; - case HTTP_Feed_Check: - case HTTP_Feed_CheckAuto: - status = L"Checking new torrents..."; - break; - case HTTP_Feed_Download: - case HTTP_Feed_DownloadAll: - status = L"Downloading torrent file..."; - break; - case HTTP_Twitter_Request: - status = L"Connecting to Twitter..."; - break; - case HTTP_Twitter_Auth: - status = L"Authorizing Twitter..."; - break; - case HTTP_Twitter_Post: - status = L"Updating Twitter status..."; - break; - case HTTP_UpdateCheck: - case HTTP_UpdateDownload: - if (m_dwTotal > 0) { - UpdateDialog.progressbar.SetPosition(m_dwDownloaded); - } - return 0; - default: - status = L"Downloading data..."; - break; - } - - if (m_dwTotal > 0) { - TaskbarList.SetProgressValue(m_dwDownloaded, m_dwTotal); - status += L" (" + ToWstr(static_cast(((float)m_dwDownloaded / (float)m_dwTotal) * 100)) + L"%)"; - } else { - status += L" (" + ToSizeString(m_dwDownloaded) + L")"; - } - - MainDialog.ChangeStatus(status); - - return 0; -} - -BOOL HttpClient::OnReadComplete() { - TaskbarList.SetProgressState(TBPF_NOPROGRESS); - wstring status; - - Stats.connections_succeeded++; - - switch (GetClientMode()) { - // List - case HTTP_MAL_RefreshList: { - wstring data = GetData(); - if (InStr(data, L"", 0, true) > -1 && - InStr(data, L"", 0, true) > -1) { - wstring path = Taiga.GetDataPath() + L"user\\" + Settings.Account.MAL.user + L"\\anime.xml"; - // Make sure the path is available - CreateFolder(GetPathOnly(path)); - // Take a backup of the previous list just in case - wstring new_path = path + L".bak"; - MoveFileEx(path.c_str(), new_path.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH); - // Save the current list - HANDLE hFile = ::CreateFile(path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile != INVALID_HANDLE_VALUE) { - DWORD dwBytesWritten = 0; - ::WriteFile(hFile, (LPCVOID)m_Buffer, m_dwDownloaded, &dwBytesWritten, NULL); - ::CloseHandle(hFile); - } - // Load the list and refresh - AnimeDatabase.LoadList(true); // Here we assume that MAL provides up-to-date series information in malappinfo.php - AnimeListDialog.RefreshList(); - AnimeListDialog.RefreshTabs(); - HistoryDialog.RefreshList(); - SearchDialog.RefreshList(); - status = L"Successfully downloaded the list."; - } else { - status = L"MyAnimeList returned an invalid response."; - } - MainDialog.ChangeStatus(status); - MainDialog.EnableInput(true); - break; - } - - // ========================================================================= - - // Login - case HTTP_MAL_Login: { - wstring username = InStr(GetData(), L"", L""); - Taiga.logged_in = IsEqual(Settings.Account.MAL.user, username); - if (Taiga.logged_in) { - Settings.Account.MAL.user = username; - status = L"Logged in as " + Settings.Account.MAL.user + L"."; - } else { - status = L"Failed to log in."; -#ifdef _DEBUG - status += L" (" + GetData() + L")"; -#else - status += L" (Invalid username or password)"; -#endif - } - MainDialog.ChangeStatus(status); - MainDialog.EnableInput(true); - MainDialog.UpdateTip(); - MainDialog.UpdateTitle(); - UpdateAllMenus(); - if (Taiga.logged_in) { - ExecuteAction(L"Synchronize"); - return TRUE; - } - break; - } - - // ========================================================================= - - // Update list - case HTTP_MAL_AnimeAdd: - case HTTP_MAL_AnimeDelete: - case HTTP_MAL_AnimeUpdate: { - History.queue.updating = false; - MainDialog.ChangeStatus(); - int anime_id = static_cast(GetParam()); - auto anime_item = AnimeDatabase.FindItem(anime_id); - auto event_item = History.queue.GetCurrentItem(); - if (anime_item && event_item) { - anime_item->Edit(*event_item, GetData(), GetResponseStatusCode()); - return TRUE; - } - break; - } - - // ========================================================================= - - // Ask to discuss - case HTTP_MAL_AnimeAskToDiscuss: { - wstring data = GetData(); - if (InStr(data, L"trueEp") > -1) { - wstring url = InStr(data, L"self.parent.document.location='", L"';"); - int anime_id = static_cast(GetParam()); - if (!url.empty()) { - int episode_number = 0; // TODO - wstring title = AnimeDatabase.FindItem(anime_id)->GetTitle(); - if (episode_number) title += L" #" + ToWstr(episode_number); - win32::TaskDialog dlg(title.c_str(), TD_ICON_INFORMATION); - dlg.SetMainInstruction(L"Someone has already made a discussion topic for this episode!"); - dlg.UseCommandLinks(true); - dlg.AddButton(L"Discuss it", IDYES); - dlg.AddButton(L"Don't discuss", IDNO); - dlg.Show(g_hMain); - if (dlg.GetSelectedButtonID() == IDYES) { - ExecuteLink(url); - } - } - } - break; - } - - // ========================================================================= - - // Anime details - case HTTP_MAL_AnimeDetails: { - int anime_id = static_cast(GetParam()); - if (mal::ParseAnimeDetails(GetData())) { - if (AnimeDialog.GetCurrentId() == anime_id) - AnimeDialog.Refresh(false, true, false); - if (NowPlayingDialog.GetCurrentId() == anime_id) - NowPlayingDialog.Refresh(false, true, false); - if (SeasonDialog.IsWindow()) - SeasonDialog.RefreshList(true); - } - break; - } - - // ========================================================================= - - // Download image - case HTTP_MAL_Image: { - int anime_id = static_cast(GetParam()); - if (ImageDatabase.Load(anime_id, true, false)) { - if (AnimeDialog.GetCurrentId() == anime_id) - AnimeDialog.Refresh(true, false, false); - if (AnimeListDialog.IsWindow()) - AnimeListDialog.RefreshListItem(anime_id); - if (NowPlayingDialog.GetCurrentId() == anime_id) - NowPlayingDialog.Refresh(true, false, false); - if (SeasonDialog.IsWindow()) - SeasonDialog.RefreshList(true); - } - break; - } - - case HTTP_MAL_UserImage: { - // TODO - break; - } - - // ========================================================================= - - // Search anime - case HTTP_MAL_SearchAnime: { - int anime_id = static_cast(GetParam()); - if (anime_id) { - if (mal::ParseSearchResult(GetData(), anime_id)) { - if (AnimeDialog.GetCurrentId() == anime_id) - AnimeDialog.Refresh(false, true, false, false); - if (NowPlayingDialog.GetCurrentId() == anime_id) - NowPlayingDialog.Refresh(false, true, false, false); - if (SeasonDialog.IsWindow()) - SeasonDialog.RefreshList(true); - auto anime_item = AnimeDatabase.FindItem(anime_id); - if (anime_item) - if (anime_item->GetGenres().empty() || anime_item->GetScore().empty()) - if (mal::GetAnimeDetails(anime_id)) - return TRUE; - } else { - status = L"Could not find anime information."; - AnimeDialog.page_series_info.SetDlgItemText(IDC_EDIT_ANIME_SYNOPSIS, status.c_str()); - } -#ifdef _DEBUG - MainDialog.ChangeStatus(status); -#endif - } else { - MainDialog.ChangeStatus(); - if (SearchDialog.IsWindow()) { - SearchDialog.ParseResults(GetData()); - } - } - break; - } - - // ========================================================================= - - // Torrent - case HTTP_Feed_Check: - case HTTP_Feed_CheckAuto: { - Feed* feed = reinterpret_cast(GetParam()); - if (feed) { - feed->Load(); - if (feed->ExamineData()) { - status = L"There are new torrents available!"; - } else { - status = L"No new torrents found."; - } - MainDialog.ChangeStatus(status); - TorrentDialog.RefreshList(); - TorrentDialog.EnableInput(); - if (GetClientMode() == HTTP_Feed_CheckAuto) { - switch (Settings.RSS.Torrent.new_action) { - // Notify - case 1: - Aggregator.Notify(*feed); - break; - // Download - case 2: - if (feed->Download(-1)) return TRUE; - break; - } - } - } - break; - } - case HTTP_Feed_Download: - case HTTP_Feed_DownloadAll: { - Feed* feed = reinterpret_cast(GetParam()); - if (feed) { - FeedItem* feed_item = reinterpret_cast(&feed->items[feed->download_index]); - wstring app_path, cmd, file = feed_item->title; - ValidateFileName(file); - file = feed->GetDataPath() + file + L".torrent"; - Aggregator.file_archive.push_back(feed_item->title); - if (FileExists(file)) { - switch (Settings.RSS.Torrent.app_mode) { - // Default application - case 1: - app_path = GetDefaultAppPath(L".torrent", L""); - break; - // Custom application - case 2: - app_path = Settings.RSS.Torrent.app_path; - break; - } - if (Settings.RSS.Torrent.set_folder && InStr(app_path, L"utorrent", 0, true) > -1) { - wstring download_path; - // Use anime folder as the download folder - auto anime_item = AnimeDatabase.FindItem(feed_item->episode_data.anime_id); - if (anime_item) { - wstring anime_folder = anime_item->GetFolder(); - if (!anime_folder.empty() && FolderExists(anime_folder)) - download_path = anime_folder; - } - // If no anime folder is set, use an alternative folder - if (download_path.empty()) { - if (Settings.RSS.Torrent.use_folder && - !Settings.RSS.Torrent.download_path.empty()) { - download_path = Settings.RSS.Torrent.download_path; - } - // Create a subfolder using the anime title as its name - if (!download_path.empty() && Settings.RSS.Torrent.create_folder) { - wstring anime_title; - if (anime_item) { - anime_title = anime_item->GetTitle(); - } else { - anime_title = feed_item->episode_data.title; - } - ValidateFileName(anime_title); - TrimRight(anime_title, L"."); - AddTrailingSlash(download_path); - download_path += anime_title; - if (!CreateFolder(download_path)) - LOG(LevelWarning, L"Subfolder could not be created."); - if (anime_item) { - anime_item->SetFolder(download_path); - Settings.Save(); - } - } - } - // Set the command line parameter - if (!download_path.empty()) - cmd = L"/directory \"" + download_path + L"\" "; - } - cmd += L"\"" + file + L"\""; - Execute(app_path, cmd); - feed_item->state = FEEDITEM_DISCARDED_NORMAL; - TorrentDialog.RefreshList(); - } - feed->download_index = -1; - if (GetClientMode() == HTTP_Feed_DownloadAll) { - if (feed->Download(-1)) return TRUE; - } - } - MainDialog.ChangeStatus(L"Successfully downloaded all torrents."); - TorrentDialog.EnableInput(); - break; - } - case HTTP_Feed_DownloadIcon: { - break; - } - - // ========================================================================= - - // Twitter - case HTTP_Twitter_Request: { - OAuthParameters response = Twitter.oauth.ParseQueryString(GetData()); - if (!response[L"oauth_token"].empty()) { - ExecuteLink(L"http://api.twitter.com/oauth/authorize?oauth_token=" + response[L"oauth_token"]); - InputDialog dlg; - dlg.title = L"Twitter Authorization"; - dlg.info = L"Please enter the PIN shown on the page after logging into Twitter:"; - dlg.Show(); - if (dlg.result == IDOK && !dlg.text.empty()) { - Twitter.AccessToken(response[L"oauth_token"], response[L"oauth_token_secret"], dlg.text); - return TRUE; - } - } else { - status = L"Twitter token request failed."; - } - MainDialog.ChangeStatus(status); - break; - } - case HTTP_Twitter_Auth: { - OAuthParameters access = Twitter.oauth.ParseQueryString(GetData()); - if (!access[L"oauth_token"].empty() && !access[L"oauth_token_secret"].empty()) { - Settings.Announce.Twitter.oauth_key = access[L"oauth_token"]; - Settings.Announce.Twitter.oauth_secret = access[L"oauth_token_secret"]; - Settings.Announce.Twitter.user = access[L"screen_name"]; - status = L"Taiga is now authorized to post to this Twitter account: "; - status += Settings.Announce.Twitter.user; - } else { - status = L"Twitter authorization failed."; - } - MainDialog.ChangeStatus(status); - SettingsDialog.RefreshTwitterLink(); - break; - } - case HTTP_Twitter_Post: { - if (InStr(GetData(), L"\"errors\"", 0) == -1) { - status = L"Twitter status updated."; - } else { - status = L"Twitter status update failed."; - int index_begin = InStr(GetData(), L"\"message\":\"", 0); - int index_end = InStr(GetData(), L"\",\"", index_begin); - if (index_begin > -1 && index_end > -1) { - index_begin += 11; - status += L" (" + GetData().substr(index_begin, index_end - index_begin) + L")"; - } - } - MainDialog.ChangeStatus(status); - break; - } - - // ========================================================================= - - // Check updates - case HTTP_UpdateCheck: { - if (Taiga.Updater.ParseData(GetData())) - if (Taiga.Updater.IsDownloadAllowed()) - return TRUE; - UpdateDialog.PostMessage(WM_CLOSE); - break; - } - - // Download updates - case HTTP_UpdateDownload: { - Taiga.Updater.RunInstaller(); - UpdateDialog.PostMessage(WM_CLOSE); - break; - } - - // ========================================================================= - - // Debug - default: { -#ifdef _DEBUG - ::MessageBox(0, GetData().c_str(), 0, 0); -#endif - } - } - - return FALSE; -} - -// ============================================================================= - -void SetProxies(const wstring& proxy, const wstring& user, const wstring& pass) { - Clients.anime.UpdateProxy(proxy, user, pass); - #define SET_PROXY(client) client.SetProxy(proxy, user, pass); - SET_PROXY(Clients.application); - SET_PROXY(Clients.service.image); - SET_PROXY(Clients.service.list); - SET_PROXY(Clients.service.search); - SET_PROXY(Clients.sharing.http); - SET_PROXY(Clients.sharing.twitter); - for (unsigned int i = 0; i < Aggregator.feeds.size(); i++) { - SET_PROXY(Aggregator.feeds[i].client); - } - SET_PROXY(Taiga.Updater.client); - #undef SET_PROXY -} - -// ============================================================================= - -AnimeClients::AnimeClients() { - clients_[HTTP_Client_Image]; - clients_[HTTP_Client_Search]; -} - -AnimeClients::~AnimeClients() { - Cleanup(true); -} - -void AnimeClients::Cleanup(bool force) { - for (auto it = clients_.begin(); it != clients_.end(); ++it) { - std::set ids; - for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { - if (force || it2->second->GetClientMode() == 0) { - ids.insert(it2->first); - } - } - for (auto id = ids.begin(); id != ids.end(); ++id) { - delete it->second[*id]; - it->second.erase(*id); - } - } -} - -class HttpClient* AnimeClients::GetClient(int type, int anime_id) { - if (anime_id <= anime::ID_UNKNOWN) - return nullptr; - - if (clients_.find(type) == clients_.end()) - return nullptr; - - auto clients = clients_[type]; - - if (clients.find(anime_id) == clients.end()) { - clients[anime_id] = new class HttpClient; - clients[anime_id]->SetProxy(Settings.Program.Proxy.host, - Settings.Program.Proxy.user, - Settings.Program.Proxy.password); -#ifdef _DEBUG - clients[anime_id]->SetUserAgent(L"Taiga/1.0 (Debug; Type " + ToWstr(type) + - L"; ID " + ToWstr(anime_id) + L")"); -#endif - } - - return clients[anime_id]; -} - -void AnimeClients::UpdateProxy(const wstring& proxy, const wstring& user, const wstring& pass) { - for (auto it = clients_.begin(); it != clients_.end(); ++it) { - for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { - it2->second->SetProxy(proxy, user, pass); - } - } -} \ No newline at end of file diff --git a/http.h b/http.h deleted file mode 100644 index 2889d414a..000000000 --- a/http.h +++ /dev/null @@ -1,112 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef HTTP_H -#define HTTP_H - -#include "std.h" - -#include "win32/win_http.h" - -enum HttpClientMode { - HTTP_Silent = 0, - // MyAnimeList - HTTP_MAL_Login, - HTTP_MAL_RefreshList, - HTTP_MAL_AnimeAdd, - HTTP_MAL_AnimeAskToDiscuss, - HTTP_MAL_AnimeDelete, - HTTP_MAL_AnimeDetails, - HTTP_MAL_AnimeUpdate, - HTTP_MAL_SearchAnime, - HTTP_MAL_Image, - HTTP_MAL_UserImage, - // Feed - HTTP_Feed_Check, - HTTP_Feed_CheckAuto, - HTTP_Feed_Download, - HTTP_Feed_DownloadAll, - HTTP_Feed_DownloadIcon, - // Twitter - HTTP_Twitter_Request, - HTTP_Twitter_Auth, - HTTP_Twitter_Post, - // Taiga - HTTP_UpdateCheck, - HTTP_UpdateDownload -}; - -enum HttpClientType { - HTTP_Client_Image = 1, - HTTP_Client_Search -}; - -// ============================================================================= - -class HttpClient : public win32::Http { -public: - HttpClient(); - virtual ~HttpClient() {} - -protected: - void OnInitialize(); - BOOL OnError(DWORD dwError); - BOOL OnSendRequestComplete(); - BOOL OnHeadersAvailable(win32::http_header_t& headers); - BOOL OnReadData(); - BOOL OnReadComplete(); - BOOL OnRedirect(wstring address); -}; - -class AnimeClients { -public: - AnimeClients(); - virtual ~AnimeClients(); - - void Cleanup(bool force); - HttpClient* GetClient(int type, int anime_id); - void UpdateProxy(const wstring& proxy, const wstring& user, const wstring& pass); - -private: - std::map> clients_; -}; - -struct HttpClients { - AnimeClients anime; - - HttpClient application; - - struct Service { - HttpClient image; - HttpClient list; - HttpClient search; - } service; - - struct Sharing { - HttpClient http; - HttpClient twitter; - } sharing; -}; - -extern HttpClients Clients; - -// ============================================================================= - -void SetProxies(const wstring& proxy, const wstring& user, const wstring& pass); - -#endif // HTTP_H \ No newline at end of file diff --git a/list_sort.cpp b/list_sort.cpp deleted file mode 100644 index b565fa53f..000000000 --- a/list_sort.cpp +++ /dev/null @@ -1,252 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "anime_db.h" -#include "common.h" -#include "settings.h" -#include "string.h" -#include "time.h" - -#include "win32/win_control.h" - -// ============================================================================= - -int CALLBACK ListViewCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { - if (!lParamSort) return 0; - - win32::ListView* m_List = reinterpret_cast(lParamSort); - int return_value = 0; - - switch (m_List->GetSortType()) { - // Number - case LIST_SORTTYPE_NUMBER: { - WCHAR szItem1[MAX_PATH], szItem2[MAX_PATH]; - m_List->GetItemText(lParam1, m_List->GetSortColumn(), szItem1); - m_List->GetItemText(lParam2, m_List->GetSortColumn(), szItem2); - int iItem1 = _wtoi(szItem1); - int iItem2 = _wtoi(szItem2); - if (iItem1 > iItem2) { - return_value = 1; - } else if (iItem1 < iItem2) { - return_value = -1; - } - break; - } - - // Popularity - case LIST_SORTTYPE_POPULARITY: { - auto pItem1 = AnimeDatabase.FindItem(static_cast(m_List->GetItemParam(lParam1))); - auto pItem2 = AnimeDatabase.FindItem(static_cast(m_List->GetItemParam(lParam2))); - if (pItem1 && pItem2) { - int iItem1 = pItem1->GetPopularity().empty() ? 0 : _wtoi(pItem1->GetPopularity().substr(1).c_str()); - int iItem2 = pItem2->GetPopularity().empty() ? 0 : _wtoi(pItem2->GetPopularity().substr(1).c_str()); - if (iItem2 == 0) { - return_value = -1; - } else if (iItem1 == 0) { - return_value = 1; - } else if (iItem1 > iItem2) { - return_value = 1; - } else if (iItem1 < iItem2) { - return_value = -1; - } - } - break; - } - - // Progress - case LIST_SORTTYPE_PROGRESS: { - auto pItem1 = AnimeDatabase.FindItem(static_cast(m_List->GetItemParam(lParam1))); - auto pItem2 = AnimeDatabase.FindItem(static_cast(m_List->GetItemParam(lParam2))); - if (pItem1 && pItem2) { - int total1 = pItem1->GetEpisodeCount(); - int total2 = pItem2->GetEpisodeCount(); - int watched1 = pItem1->GetMyLastWatchedEpisode(); - int watched2 = pItem2->GetMyLastWatchedEpisode(); - bool available1 = pItem1->IsNewEpisodeAvailable(); - bool available2 = pItem2->IsNewEpisodeAvailable(); - if (available1 && !available2) { - return_value = -1; - } else if (!available1 && available2) { - return_value = 1; - } else if (total1 && total2) { - float ratio1 = static_cast(watched1) / static_cast(total1); - float ratio2 = static_cast(watched2) / static_cast(total2); - if (ratio1 > ratio2) { - return_value = -1; - } else if (ratio1 < ratio2) { - return_value = 1; - } else { - if (total1 > total2) { - return_value = -1; - } else if (total1 < total2) { - return_value = 1; - } - } - } else { - if (watched1 > watched2) { - return_value = -1; - } else if (watched1 < watched2) { - return_value = 1; - } else { - if (total1 > total2) { - return_value = -1; - } else if (total1 < total2) { - return_value = 1; - } - } - } - } - break; - } - - // Episodes - case LIST_SORTTYPE_EPISODES: { - auto pItem1 = AnimeDatabase.FindItem(static_cast(m_List->GetItemParam(lParam1))); - auto pItem2 = AnimeDatabase.FindItem(static_cast(m_List->GetItemParam(lParam2))); - if (pItem1 && pItem2) { - if (pItem1->GetEpisodeCount() > pItem2->GetEpisodeCount()) { - return_value = 1; - } else if (pItem1->GetEpisodeCount() < pItem2->GetEpisodeCount()) { - return_value = -1; - } - } - break; - } - - // Score - case LIST_SORTTYPE_SCORE: { - auto pItem1 = AnimeDatabase.FindItem(static_cast(m_List->GetItemParam(lParam1))); - auto pItem2 = AnimeDatabase.FindItem(static_cast(m_List->GetItemParam(lParam2))); - if (pItem1 && pItem2) { - return_value = lstrcmpi(pItem1->GetScore().c_str(), pItem2->GetScore().c_str()); - } - break; - } - - // Start date - case LIST_SORTTYPE_STARTDATE: { - auto pItem1 = AnimeDatabase.FindItem(static_cast(m_List->GetItemParam(lParam1))); - auto pItem2 = AnimeDatabase.FindItem(static_cast(m_List->GetItemParam(lParam2))); - if (pItem1 && pItem2) { - Date date1 = pItem1->GetDate(anime::DATE_START); - Date date2 = pItem2->GetDate(anime::DATE_START); - if (date1 != date2) { - if (!date1.year) date1.year = static_cast(-1); // Hello. - if (!date2.year) date2.year = static_cast(-1); // We come from the future. - if (!date1.month) date1.month = 12; - if (!date2.month) date2.month = 12; - if (!date1.day) date1.day = 31; - if (!date2.day) date2.day = 31; - return_value = date2 > date1 ? 1 : -1; - } - } - break; - } - - // Last updated - case LIST_SORTTYPE_LASTUPDATED: { - auto pItem1 = AnimeDatabase.FindItem(static_cast(m_List->GetItemParam(lParam1))); - auto pItem2 = AnimeDatabase.FindItem(static_cast(m_List->GetItemParam(lParam2))); - if (pItem1 && pItem2) { - time_t time1 = _wtoi64(pItem1->GetMyLastUpdated().c_str()); - time_t time2 = _wtoi64(pItem2->GetMyLastUpdated().c_str()); - if (time1 > time2) { - return_value = 1; - } else if (time1 < time2) { - return_value = -1; - } - } - break; - } - - // File size - case LIST_SORTTYPE_FILESIZE: { - wstring item[2], unit[2]; - UINT64 size[2] = {1, 1}; - m_List->GetItemText(lParam1, m_List->GetSortColumn(), item[0]); - m_List->GetItemText(lParam2, m_List->GetSortColumn(), item[1]); - for (size_t i = 0; i < 2; i++) { - TrimRight(item[i], L".\r"); - EraseChars(item[i], L" "); - if (item[i].length() >= 2) { - for (auto it = item[i].rbegin(); it != item[i].rend(); ++it) { - if (IsNumeric(*it)) - break; - unit[i].insert(unit[i].begin(), *it); - } - item[i].resize(item[i].length() - unit[i].length()); - Trim(unit[i]); - } - int index = InStr(item[i], L"."); - if (index > -1) { - int length = item[i].substr(index + 1).length(); - if (length <= 2) item[i].append(2 - length, '0'); - EraseChars(item[i], L"."); - } else { - item[i].append(2, '0'); - } - if (IsEqual(unit[i], L"KB")) { - size[i] *= 1000; - } else if (IsEqual(unit[i], L"KiB")) { - size[i] *= 1024; - } else if (IsEqual(unit[i], L"MB")) { - size[i] *= 1000 * 1000; - } else if (IsEqual(unit[i], L"MiB")) { - size[i] *= 1024 * 1024; - } else if (IsEqual(unit[i], L"GB")) { - size[i] *= 1000 * 1000 * 1000; - } else if (IsEqual(unit[i], L"GiB")) { - size[i] *= 1024 * 1024 * 1024; - } - size[i] *= _wtoi(item[i].c_str()); - } - if (size[0] > size[1]) { - return_value = 1; - } else if (size[0] < size[1]) { - return_value = -1; - } - break; - } - - // Title - case LIST_SORTTYPE_TITLE: { - auto pItem1 = AnimeDatabase.FindItem(static_cast(m_List->GetItemParam(lParam1))); - auto pItem2 = AnimeDatabase.FindItem(static_cast(m_List->GetItemParam(lParam2))); - if (pItem1 && pItem2) { - if (Settings.Program.List.english_titles) { - return_value = CompareStrings(pItem1->GetEnglishTitle(true), pItem2->GetEnglishTitle(true)); - } else { - return_value = CompareStrings(pItem1->GetTitle(), pItem2->GetTitle()); - } - } - break; - } - - // Text - case LIST_SORTTYPE_DEFAULT: - default: - WCHAR szItem1[MAX_PATH], szItem2[MAX_PATH]; - m_List->GetItemText(lParam1, m_List->GetSortColumn(), szItem1); - m_List->GetItemText(lParam2, m_List->GetSortColumn(), szItem2); - return_value = lstrcmpi(szItem1, szItem2); - } - - return return_value * m_List->GetSortOrder(); -} \ No newline at end of file diff --git a/logger.cpp b/logger.cpp deleted file mode 100644 index aaf428df6..000000000 --- a/logger.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" -#include -#include -#include "logger.h" -#include "string.h" -#include "time.h" - -const char* SeverityLevels[] = { - "Emergency", - "Alert", - "Critical", - "Error", - "Warning", - "Notice", - "Informational", - "Debug" -}; - -class Logger Logger; - -Logger::Logger() - : severity_level_(LevelDebug) { -} - -void Logger::Log(int severity_level, const wstring& file, int line, - const wstring& function, const wstring& text) { - critical_section_.Enter(); - - if (severity_level <= severity_level_) { - string output_text; - - output_text += ToANSI(wstring(GetDate()) + L" " + GetTime() + L" "); - output_text += "[" + string(SeverityLevels[severity_level]) + "] "; - output_text += ToANSI(GetFileName(file) + L":" + ToWstr(line) + L" " + function + L" | "); - output_text += ToANSI(text + L"\r\n"); - -#ifdef _DEBUG - OutputDebugStringA(output_text.c_str()); -#endif - - if (!output_path_.empty()) { - std::ofstream stream; - stream.open(output_path_, std::ofstream::app | - std::ios::binary | - std::ofstream::out); - if (stream.is_open()) { - stream.write(output_text.c_str(), output_text.size()); - stream.close(); - } - } - } - - critical_section_.Leave(); -} - -void Logger::SetOutputPath(const wstring& path) { - output_path_ = path; -} - -void Logger::SetSeverityLevel(int severity_level) { - severity_level_ = severity_level; -} \ No newline at end of file diff --git a/media.h b/media.h deleted file mode 100644 index eb9b66615..000000000 --- a/media.h +++ /dev/null @@ -1,89 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef MEDIA_H -#define MEDIA_H - -#include "std.h" -#include "accessibility.h" - -// ============================================================================= - -enum MediaPlayerModes { - MEDIA_MODE_WINDOWTITLE, - MEDIA_MODE_FILEHANDLE, - MEDIA_MODE_WINAMPAPI, - MEDIA_MODE_SPECIALMESSAGE, - MEDIA_MODE_MPLAYER, - MEDIA_MODE_WEBBROWSER -}; - -/* Media players class */ - -class MediaPlayers { -public: - MediaPlayers(); - virtual ~MediaPlayers() {} - - BOOL Load(); - int Check(); - - void EditTitle(wstring& str, int player_index); - wstring GetTitle(HWND hwnd, const wstring& class_name, int mode); - bool TitleChanged() const; - void SetTitleChanged(bool title_changed); - - wstring GetTitleFromProcessHandle(HWND hwnd, ULONG process_id = 0); - wstring GetTitleFromWinampAPI(HWND hwnd, bool use_unicode); - wstring GetTitleFromSpecialMessage(HWND hwnd, const wstring& class_name); - wstring GetTitleFromMPlayer(); - wstring GetTitleFromBrowser(HWND hwnd); - -public: - int index, index_old; - wstring current_title, new_title; - - class MediaPlayer { - public: - wstring engine, name; - BOOL enabled, visible; - int mode; - HWND window_handle; - vector classes, files, folders; - wstring GetPath(); - class EditTitle { - public: - int mode; - wstring value; - }; - vector edits; - }; - vector items; - - class BrowserAccessibleObject : public AccessibleObject { - public: - bool AllowChildTraverse(AccessibleChild& child, LPARAM param = 0L); - } acc_obj; - -private: - bool title_changed_; -}; - -extern MediaPlayers MediaPlayers; - -#endif // MEDIA_H \ No newline at end of file diff --git a/mediaplayers.cpp b/mediaplayers.cpp deleted file mode 100644 index 5ef2fbfae..000000000 --- a/mediaplayers.cpp +++ /dev/null @@ -1,570 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" -#include - -#include "media.h" - -#include "anime.h" -#include "common.h" -#include "process.h" -#include "recognition.h" -#include "settings.h" -#include "string.h" -#include "taiga.h" -#include "xml.h" - -class MediaPlayers MediaPlayers; - -// ============================================================================= - -MediaPlayers::MediaPlayers() - : index(-1), - index_old(-1), - title_changed_(false) { -} - -BOOL MediaPlayers::Load() { - // Initialize - wstring file = Taiga.GetDataPath() + L"media.xml"; - items.clear(); - index = -1; - - // Load XML file - xml_document doc; - xml_parse_result result = doc.load_file(file.c_str()); - if (result.status != status_ok) { - MessageBox(NULL, L"Could not read media list.", file.c_str(), MB_OK | MB_ICONERROR); - return FALSE; - } - - // Read player list - xml_node mediaplayers = doc.child(L"media_players"); - for (xml_node player = mediaplayers.child(L"player"); player; player = player.next_sibling(L"player")) { - items.resize(items.size() + 1); - items.back().name = XML_ReadStrValue(player, L"name"); - items.back().enabled = XML_ReadIntValue(player, L"enabled"); - items.back().engine = XML_ReadStrValue(player, L"engine"); - items.back().visible = XML_ReadIntValue(player, L"visible"); - items.back().mode = XML_ReadIntValue(player, L"mode"); - XML_ReadChildNodes(player, items.back().classes, L"class"); - XML_ReadChildNodes(player, items.back().files, L"file"); - XML_ReadChildNodes(player, items.back().folders, L"folder"); - for (xml_node child_node = player.child(L"edit"); child_node; child_node = child_node.next_sibling(L"edit")) { - MediaPlayer::EditTitle edit; - edit.mode = child_node.attribute(L"mode").as_int(); - edit.value = child_node.child_value(); - items.back().edits.push_back(edit); - } - } - - return TRUE; -} - -// ============================================================================= - -int MediaPlayers::Check() { - index = -1; - bool recognized = CurrentEpisode.anime_id > anime::ID_UNKNOWN; - - // Go through windows, starting with the highest in the Z order - HWND hwnd = GetWindow(g_hMain, GW_HWNDFIRST); - while (hwnd != nullptr) { - for (auto item = items.begin(); item != items.end(); ++item) { - if (!item->enabled) - continue; - if (item->visible && !IsWindowVisible(hwnd)) - continue; - // Compare window classes - for (auto c = item->classes.begin(); c != item->classes.end(); ++c) { - if (*c == GetWindowClass(hwnd)) { - // Compare file names - for (auto f = item->files.begin(); f != item->files.end(); ++f) { - if (IsEqual(*f, GetFileName(GetWindowPath(hwnd)))) { - if (item->mode != MEDIA_MODE_WEBBROWSER || !GetWindowTitle(hwnd).empty()) { - // Stick with the previously recognized window, if there is one - if (!recognized || item->window_handle == hwnd) { - // We have a match! - index = index_old = item - items.begin(); - new_title = GetTitle(hwnd, *c, item->mode); - EditTitle(new_title, index); - if (current_title != new_title) title_changed_ = true; - current_title = new_title; - item->window_handle = hwnd; - return index; - } - } - } - } - } - } - } - // Check next window - hwnd = GetWindow(hwnd, GW_HWNDNEXT); - } - - // Not found - return -1; -} - -void MediaPlayers::EditTitle(wstring& str, int player_index) { - if (str.empty() || items[player_index].edits.empty()) return; - - for (unsigned int i = 0; i < items[player_index].edits.size(); i++) { - switch (items[player_index].edits[i].mode) { - // Erase - case 1: { - Replace(str, items[player_index].edits[i].value, L"", false, true); - break; - } - // Cut right side - case 2: { - int pos = InStr(str, items[player_index].edits[i].value, 0); - if (pos > -1) str.resize(pos); - break; - } - } - } - - TrimRight(str, L" -"); -} - -wstring MediaPlayers::MediaPlayer::GetPath() { - for (size_t i = 0; i < folders.size(); i++) { - for (size_t j = 0; j < files.size(); j++) { - wstring path = folders[i] + files[j]; - path = ExpandEnvironmentStrings(path); - if (FileExists(path)) return path; - } - } - return L""; -} - -wstring MediaPlayers::GetTitle(HWND hwnd, const wstring& class_name, int mode) { - switch (mode) { - // File handle - case MEDIA_MODE_FILEHANDLE: - return GetTitleFromProcessHandle(hwnd); - // Winamp API - case MEDIA_MODE_WINAMPAPI: - return GetTitleFromWinampAPI(hwnd, false); - // Special message - case MEDIA_MODE_SPECIALMESSAGE: - return GetTitleFromSpecialMessage(hwnd, class_name); - // MPlayer - case MEDIA_MODE_MPLAYER: - return GetTitleFromMPlayer(); - // Browser - case MEDIA_MODE_WEBBROWSER: - return GetTitleFromBrowser(hwnd); - // Window title - case MEDIA_MODE_WINDOWTITLE: - default: - return GetWindowTitle(hwnd); - } -} - -bool MediaPlayers::TitleChanged() const { - return title_changed_; -} - -void MediaPlayers::SetTitleChanged(bool title_changed) { - title_changed_ = title_changed; -} - -// ============================================================================= - -// BS.Player -#define BSP_CLASS L"BSPlayer" -#define BSP_GETFILENAME 0x1010B - -// JetAudio -#define JETAUDIO_REMOTECLASS L"COWON Jet-Audio Remocon Class" -#define JETAUDIO_MAINCLASS L"COWON Jet-Audio MainWnd Class" -#define JETAUDIO_WINDOW L"Jet-Audio Remote Control" -#define WM_REMOCON_GETSTATUS WM_APP + 740 -#define GET_STATUS_STATUS 1 -#define GET_STATUS_TRACK_FILENAME 11 - -// Winamp -#define IPC_ISPLAYING 104 -#define IPC_GETLISTPOS 125 -#define IPC_GETPLAYLISTFILE 211 -#define IPC_GETPLAYLISTFILEW 214 - -wstring MediaPlayers::GetTitleFromProcessHandle(HWND hwnd, ULONG process_id) { - vector files_vector; - if (hwnd != NULL && process_id == 0) { - GetWindowThreadProcessId(hwnd, &process_id); - } - if (GetProcessFiles(process_id, files_vector)) { - for (unsigned int i = 0; i < files_vector.size(); i++) { - if (CheckFileExtension(GetFileExtension(files_vector[i]), Meow.valid_extensions)) { - if (files_vector[i].at(1) != L':') { - TranslateDeviceName(files_vector[i]); - } - if (files_vector[i].at(1) == L':') { - WCHAR buffer[4096] = {0}; - GetLongPathName(files_vector[i].c_str(), buffer, 4096); - return wstring(buffer); - } else { - return GetFileName(files_vector[i]); - } - } - } - } - return L""; -} - -wstring MediaPlayers::GetTitleFromWinampAPI(HWND hwnd, bool use_unicode) { - if (IsWindow(hwnd)) { - if (SendMessage(hwnd, WM_USER, 0, IPC_ISPLAYING)) { - int list_index = SendMessage(hwnd, WM_USER, 0, IPC_GETLISTPOS); - int base_address = SendMessage(hwnd, WM_USER, list_index, - use_unicode ? IPC_GETPLAYLISTFILEW : IPC_GETPLAYLISTFILE); - if (base_address) { - DWORD process_id; GetWindowThreadProcessId(hwnd, &process_id); - HANDLE hwnd_winamp = OpenProcess(PROCESS_VM_READ, FALSE, process_id); - if (hwnd_winamp) { - if (use_unicode) { - wchar_t file_name[MAX_PATH]; - ReadProcessMemory(hwnd_winamp, reinterpret_cast(base_address), - file_name, MAX_PATH, NULL); - CloseHandle(hwnd_winamp); - return file_name; - } else { - char file_name[MAX_PATH]; - ReadProcessMemory(hwnd_winamp, reinterpret_cast(base_address), - file_name, MAX_PATH, NULL); - CloseHandle(hwnd_winamp); - return ToUTF8(file_name); - } - } - } - } - } - return L""; -} - -wstring MediaPlayers::GetTitleFromSpecialMessage(HWND hwnd, const wstring& class_name) { - // BS.Player - if (class_name == BSP_CLASS) { - if (IsWindow(hwnd)) { - COPYDATASTRUCT cds; - char file_name[MAX_PATH]; - void* data = &file_name; - cds.dwData = BSP_GETFILENAME; - cds.lpData = &data; - cds.cbData = 4; - SendMessage(hwnd, WM_COPYDATA, reinterpret_cast(g_hMain), - reinterpret_cast(&cds)); - return ToUTF8(file_name); - } - - // JetAudio - } else if (class_name == JETAUDIO_MAINCLASS) { - HWND hwnd_remote = FindWindow(JETAUDIO_REMOTECLASS, JETAUDIO_WINDOW); - if (IsWindow(hwnd_remote)) { - if (SendMessage(hwnd_remote, WM_REMOCON_GETSTATUS, 0, GET_STATUS_STATUS) != MCI_MODE_STOP) { - if (SendMessage(hwnd_remote, WM_REMOCON_GETSTATUS, - reinterpret_cast(g_hMain), GET_STATUS_TRACK_FILENAME)) { - return new_title; - } - } - } - } - - return L""; -} - -wstring MediaPlayers::GetTitleFromMPlayer() { - HANDLE hProcessSnap; - PROCESSENTRY32 pe32; - wstring title; - - hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hProcessSnap != INVALID_HANDLE_VALUE) { - pe32.dwSize = sizeof(PROCESSENTRY32); - if (Process32First(hProcessSnap, &pe32)) { - do { - if (IsEqual(pe32.szExeFile, L"mplayer.exe")) { - title = GetTitleFromProcessHandle(NULL, pe32.th32ProcessID); - break; - } - } while (Process32Next(hProcessSnap, &pe32)); - } - CloseHandle(hProcessSnap); - } - - return title; -} - -// ============================================================================= - -/* Streaming media recognition */ - -enum StreamingVideoProviders { - STREAM_UNKNOWN = -1, - STREAM_ANN, - STREAM_CRUNCHYROLL, - STREAM_HULU, - STREAM_VEOH, - STREAM_VIZANIME, - STREAM_YOUTUBE -}; - -enum WebBrowserEngines { - WEBENGINE_UNKNOWN = -1, - // Google Chrome (and other browsers based on Chromium) - WEBENGINE_WEBKIT, - // Mozilla Firefox - WEBENGINE_GECKO, - // Internet Explorer - WEBENGINE_TRIDENT, - // Opera (older versions) - WEBENGINE_PRESTO -}; - -AccessibleChild* FindAccessibleChild(vector& children, const wstring& name, const wstring& role) { - AccessibleChild* child = nullptr; - - for (auto it = children.begin(); it != children.end(); ++it) { - if (name.empty() || IsEqual(name, it->name)) { - if (role.empty() || IsEqual(role, it->role)) { - child = &(*it); - } - } - if (child == nullptr && !it->children.empty()) { - child = FindAccessibleChild(it->children, name, role); - } - if (child) { - break; - } - } - - return child; -} - -#ifdef _DEBUG -void BuildTreeString(vector& children, wstring& str, int indent) { - for (auto it = children.begin(); it != children.end(); ++it) { - str.append(indent * 4, L' '); - str += L"[" + it->role + L"] " + it->name + L" = " + it->value + L"\n"; - BuildTreeString(it->children, str, indent + 1); - } -} -#endif - -wstring MediaPlayers::GetTitleFromBrowser(HWND hwnd) { - int stream_provider = STREAM_UNKNOWN; - int web_engine = WEBENGINE_UNKNOWN; - - // Get window title - wstring title = GetWindowTitle(hwnd); - EditTitle(title, index); - - // Return current title if the same web page is still open - if (CurrentEpisode.anime_id > 0) - if (InStr(title, current_title) > -1) - return current_title; - - // Delay operation to save some CPU - static int counter = 0; - if (counter < 5) { - counter++; - return current_title; - } else { - counter = 0; - } - - // Select web browser engine - if (items.at(index).engine == L"WebKit") { - web_engine = WEBENGINE_WEBKIT; - } else if (items.at(index).engine == L"Gecko") { - web_engine = WEBENGINE_GECKO; - } else if (items.at(index).engine == L"Trident") { - web_engine = WEBENGINE_TRIDENT; - } else if (items.at(index).engine == L"Presto") { - web_engine = WEBENGINE_PRESTO; - } else { - return L""; - } - - // Build accessibility data - acc_obj.children.clear(); - if (acc_obj.FromWindow(hwnd) == S_OK) { - acc_obj.BuildChildren(acc_obj.children, nullptr, web_engine); - acc_obj.Release(); - } - - // Check other tabs - if (CurrentEpisode.anime_id > 0) { - AccessibleChild* child = nullptr; - switch (web_engine) { - case WEBENGINE_WEBKIT: - case WEBENGINE_GECKO: - child = FindAccessibleChild(acc_obj.children, L"", L"page tab list"); - break; - case WEBENGINE_TRIDENT: - child = FindAccessibleChild(acc_obj.children, L"Tab Row", L""); - break; - case WEBENGINE_PRESTO: - child = FindAccessibleChild(acc_obj.children, L"", L"client"); - break; - } - if (child) { - for (auto it = child->children.begin(); it != child->children.end(); ++it) { - if (InStr(it->name, current_title) > -1) { - // Tab is still open, just not active - return current_title; - } - } - } - // Tab is closed - return L""; - } - - // Find URL - AccessibleChild* child = nullptr; - switch (web_engine) { - case WEBENGINE_WEBKIT: - child = FindAccessibleChild(acc_obj.children, L"Address and search bar", L"grouping"); - if (child == nullptr) - child = FindAccessibleChild(acc_obj.children, L"Address", L"grouping"); - if (child == nullptr) - child = FindAccessibleChild(acc_obj.children, L"Location", L"grouping"); - if (child == nullptr) - child = FindAccessibleChild(acc_obj.children, L"Address field", L"editable text"); - break; - case WEBENGINE_GECKO: - child = FindAccessibleChild(acc_obj.children, L"Search or enter address", L"editable text"); - if (child == nullptr) - child = FindAccessibleChild(acc_obj.children, L"Go to a Website", L"editable text"); - if (child == nullptr) - child = FindAccessibleChild(acc_obj.children, L"Go to a Web Site", L"editable text"); - break; - case WEBENGINE_TRIDENT: - child = FindAccessibleChild(acc_obj.children, L"Address and search using Bing", L"editable text"); - if (child == nullptr) - child = FindAccessibleChild(acc_obj.children, L"Address and search using Google", L"editable text"); - break; - case WEBENGINE_PRESTO: - child = FindAccessibleChild(acc_obj.children, L"", L"client"); - if (child && !child->children.empty()) { - child = FindAccessibleChild(child->children.at(0).children, L"", L"tool bar"); - if (child && !child->children.empty()) { - child = FindAccessibleChild(child->children, L"", L"combo box"); - if (child && !child->children.empty()) { - child = FindAccessibleChild(child->children, L"", L"editable text"); - } - } - } - break; - } - - // Check URL for known streaming video providers - if (child) { - // Anime News Network - if (Settings.Recognition.Streaming.ann_enabled && - InStr(child->value, L"animenewsnetwork.com/video") > -1) { - stream_provider = STREAM_ANN; - // Crunchyroll - } else if (Settings.Recognition.Streaming.crunchyroll_enabled && - InStr(child->value, L"crunchyroll.com/") > -1) { - stream_provider = STREAM_CRUNCHYROLL; - // Hulu - /* - } else if (InStr(child->value, L"hulu.com/watch") > -1) { - stream_provider = STREAM_HULU; - */ - // Veoh - } else if (Settings.Recognition.Streaming.veoh_enabled && - InStr(child->value, L"veoh.com/watch") > -1) { - stream_provider = STREAM_VEOH; - // Viz Anime - } else if (Settings.Recognition.Streaming.viz_enabled && - InStr(child->value, L"vizanime.com/ep") > -1) { - stream_provider = STREAM_VIZANIME; - // YouTube - } else if (Settings.Recognition.Streaming.youtube_enabled && - InStr(child->value, L"youtube.com/watch") > -1) { - stream_provider = STREAM_YOUTUBE; - } - } - - // Clean-up title - switch (stream_provider) { - // Anime News Network - case STREAM_ANN: - EraseRight(title, L" - Anime News Network"); - Erase(title, L" (s)"); - Erase(title, L" (d)"); - break; - // Crunchyroll - case STREAM_CRUNCHYROLL: - EraseLeft(title, L"Crunchyroll - Watch "); - break; - // Hulu - case STREAM_HULU: - EraseLeft(title, L"Watch "); - EraseRight(title, L" online | Free | Hulu"); - EraseRight(title, L" online | Plus | Hulu"); - break; - // Veoh - case STREAM_VEOH: - EraseLeft(title, L"Watch Videos Online | "); - EraseRight(title, L" | Veoh.com"); - break; - // Viz Anime - case STREAM_VIZANIME: - EraseRight(title, L" - VIZ ANIME: Free Online Anime - All The Time"); - break; - // YouTube - case STREAM_YOUTUBE: - EraseRight(title, L" - YouTube"); - break; - // Some other website, or URL is not found - case STREAM_UNKNOWN: - title.clear(); - break; - } - - return title; -} - -bool MediaPlayers::BrowserAccessibleObject::AllowChildTraverse(AccessibleChild& child, LPARAM param) { - switch (param) { - case WEBENGINE_UNKNOWN: - return false; - case WEBENGINE_GECKO: - if (IsEqual(child.role, L"document")) - return false; - break; - case WEBENGINE_TRIDENT: - if (IsEqual(child.role, L"pane") || IsEqual(child.role, L"scroll bar")) - return false; - break; - case WEBENGINE_PRESTO: - if (IsEqual(child.role, L"document") || IsEqual(child.role, L"pane")) - return false; - break; - } - - return true; -} \ No newline at end of file diff --git a/menu.cpp b/menu.cpp deleted file mode 100644 index 76463d39d..000000000 --- a/menu.cpp +++ /dev/null @@ -1,307 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "anime_db.h" -#include "anime_filter.h" -#include "myanimelist.h" -#include "settings.h" -#include "string.h" -#include "taiga.h" -#include "theme.h" - -#include "dlg/dlg_main.h" -#include "dlg/dlg_season.h" - -#define MENU UI.Menus.Menu[menu_index] - -// ============================================================================= - -void UpdateAnimeMenu(anime::Item* anime_item) { - if (!anime_item) return; - if (!anime_item->IsInList()) return; - int item_index = 0, menu_index = 0; - - // Edit > Score - menu_index = UI.Menus.GetIndex(L"EditScore"); - if (menu_index > -1) { - for (unsigned int i = 0; i < MENU.Items.size(); i++) { - MENU.Items[i].Checked = false; - MENU.Items[i].Default = false; - } - item_index = anime_item->GetMyScore(); - if (item_index < static_cast(MENU.Items.size())) { - MENU.Items[item_index].Checked = true; - MENU.Items[item_index].Default = true; - } - } - // Edit > Status - menu_index = UI.Menus.GetIndex(L"EditStatus"); - if (menu_index > -1) { - for (unsigned int i = 0; i < MENU.Items.size(); i++) { - MENU.Items[i].Checked = false; - MENU.Items[i].Default = false; - } - item_index = anime_item->GetMyStatus(); - if (item_index == mal::MYSTATUS_PLANTOWATCH) item_index--; - if (item_index - 1 < static_cast(MENU.Items.size())) { - MENU.Items[item_index - 1].Checked = true; - MENU.Items[item_index - 1].Default = true; - } - } - - // Play - menu_index = UI.Menus.GetIndex(L"RightClick"); - if (menu_index > -1) { - for (int i = static_cast(MENU.Items.size()) - 1; i > 0; i--) { - if (MENU.Items[i].Type == win32::MENU_ITEM_SEPARATOR) { - // Clear items - MENU.Items.resize(i + 1); - // Play episode - if (anime_item->GetEpisodeCount() != 1) { - MENU.CreateItem(L"", L"Play episode", L"PlayEpisode"); - } - // Play last episode - if (anime_item->GetMyLastWatchedEpisode() > 0) { - MENU.CreateItem( - L"PlayLast()", - L"Play last episode (#" + ToWstr(anime_item->GetMyLastWatchedEpisode()) + L")"); - } - // Play next episode - if (anime_item->GetEpisodeCount() == 0 || - anime_item->GetMyLastWatchedEpisode() < anime_item->GetEpisodeCount()) { - MENU.CreateItem( - L"PlayNext()", - L"Play next episode (#" + ToWstr(anime_item->GetMyLastWatchedEpisode() + 1) + L")"); - } - // Play random episode - if (anime_item->GetEpisodeCount() != 1) { - MENU.CreateItem(L"PlayRandom()", L"Play random episode"); - } - break; - } - } - } - - // Play > Episode - menu_index = UI.Menus.GetIndex(L"PlayEpisode"); - if (menu_index > -1) { - // Clear menu - MENU.Items.clear(); - - // Add episode numbers - int count_max, count_column; - if (anime_item->GetEpisodeCount() > 0) { - count_max = anime_item->GetEpisodeCount(); - } else { - count_max = anime_item->GetEpisodeCount(); - if (count_max == 0) { - count_max = anime_item->GetMyLastWatchedEpisode() + 1; - } - } - for (int i = 1; i <= count_max; i++) { - count_column = count_max % 12 == 0 ? 12 : 13; - if (count_max > 52) count_column *= 2; - MENU.CreateItem(L"PlayEpisode(" + ToWstr(i) + L")", L"#" + ToWstr(i), L"", - i <= anime_item->GetMyLastWatchedEpisode(), - false, true, (i > 1) && (i % count_column == 1), false); - } - } -} - -void UpdateAnnounceMenu() { - // List > Announce current episode - int menu_index = UI.Menus.GetIndex(L"List"); - if (menu_index > -1) { - for (unsigned int i = 0; i < MENU.Items.size(); i++) { - if (MENU.Items[i].SubMenu == L"Announce") { - MENU.Items[i].Enabled = CurrentEpisode.anime_id > 0; - break; - } - } - } -} - -void UpdateExternalLinksMenu() { - int menu_index = UI.Menus.GetIndex(L"ExternalLinks"); - if (menu_index > -1) { - // Clear menu - MENU.Items.clear(); - - vector lines; - Split(Settings.Program.General.external_links, L"\r\n", lines); - for (auto line = lines.begin(); line != lines.end(); ++line) { - if (IsEqual(*line, L"-")) { - // Add separator - MENU.CreateItem(); - } else { - vector content; - Split(*line, L"|", content); - if (content.size() > 1) { - MENU.CreateItem(L"URL(" + content.at(1) + L")", content.at(0)); - } - } - } - } -} - -void UpdateFoldersMenu() { - int menu_index = UI.Menus.GetIndex(L"Folders"); - if (menu_index > -1) { - // Clear menu - MENU.Items.clear(); - - if (!Settings.Folders.root.empty()) { - // Add folders - for (unsigned int i = 0; i < Settings.Folders.root.size(); i++) { - MENU.CreateItem(L"Execute(" + Settings.Folders.root[i] + L")", Settings.Folders.root[i]); - } - // Add separator - MENU.CreateItem(); - } - - // Add default item - MENU.CreateItem(L"AddFolder()", L"Add new folder..."); - } -} - -void UpdateSearchListMenu(bool enabled) { - int menu_index = UI.Menus.GetIndex(L"SearchList"); - if (menu_index > -1) { - // Add to list - for (size_t i = 0; i < MENU.Items.size(); i++) { - if (MENU.Items[i].SubMenu == L"AddToList") { - MENU.Items[i].Enabled = enabled; - break; - } - } - } -} - -void UpdateSeasonListMenu(bool enabled) { - int menu_index = UI.Menus.GetIndex(L"SeasonList"); - if (menu_index > -1) { - // Add to list - for (size_t i = 0; i < MENU.Items.size(); i++) { - if (MENU.Items[i].SubMenu == L"AddToList") { - MENU.Items[i].Enabled = enabled; - break; - } - } - } -} - -void UpdateSeasonMenu() { - int item_index, menu_index = -1; - - // Group by - menu_index = UI.Menus.GetIndex(L"SeasonGroup"); - if (menu_index > -1) { - for (unsigned int i = 0; i < MENU.Items.size(); i++) { - MENU.Items[i].Checked = false; - } - item_index = SeasonDialog.group_by; - if (item_index < static_cast(MENU.Items.size())) { - MENU.Items[item_index].Checked = true; - } - } - - // Sort by - menu_index = UI.Menus.GetIndex(L"SeasonSort"); - if (menu_index > -1) { - for (unsigned int i = 0; i < MENU.Items.size(); i++) { - MENU.Items[i].Checked = false; - } - item_index = SeasonDialog.sort_by; - if (item_index < static_cast(MENU.Items.size())) { - MENU.Items[item_index].Checked = true; - } - } - - // View as - menu_index = UI.Menus.GetIndex(L"SeasonView"); - if (menu_index > -1) { - for (unsigned int i = 0; i < MENU.Items.size(); i++) { - MENU.Items[i].Checked = false; - } - item_index = SeasonDialog.view_as; - if (item_index < static_cast(MENU.Items.size())) { - MENU.Items[item_index].Checked = true; - } - } -} - -void UpdateToolsMenu() { - int menu_index = UI.Menus.GetIndex(L"Tools"); - if (menu_index > -1) { - for (unsigned int i = 0; i < MENU.Items.size(); i++) { - // Tools > Enable anime recognition - if (MENU.Items[i].Action == L"ToggleRecognition()") - MENU.Items[i].Checked = Settings.Program.General.enable_recognition == TRUE; - // Tools > Enable auto sharing - if (MENU.Items[i].Action == L"ToggleSharing()") - MENU.Items[i].Checked = Settings.Program.General.enable_sharing == TRUE; - // Tools > Enable auto synchronization - if (MENU.Items[i].Action == L"ToggleSynchronization()") - MENU.Items[i].Checked = Settings.Program.General.enable_sync == TRUE; - } - } -} - -void UpdateTrayMenu() { - int menu_index = UI.Menus.GetIndex(L"Tray"); - if (menu_index > -1) { - // Tray > Enable recognition - for (unsigned int i = 0; i < MENU.Items.size(); i++) { - if (MENU.Items[i].Action == L"ToggleRecognition()") { - MENU.Items[i].Checked = Settings.Program.General.enable_recognition == TRUE; - break; - } - } - } -} - -void UpdateViewMenu() { - int item_index, menu_index = -1; - - menu_index = UI.Menus.GetIndex(L"View"); - if (menu_index > -1) { - for (unsigned int i = 0; i < MENU.Items.size(); i++) { - MENU.Items[i].Checked = false; - } - item_index = MainDialog.navigation.GetCurrentPage(); - for (unsigned int i = 0; i < MENU.Items.size(); i++) { - if (MENU.Items[i].Action == L"ViewContent(" + ToWstr(item_index) + L")") { - MENU.Items[i].Checked = true; - break; - } - } - MENU.Items.back().Checked = Settings.Program.General.hide_sidebar == FALSE; - } -} - -void UpdateAllMenus(anime::Item* anime_item) { - UpdateAnimeMenu(anime_item); - UpdateAnnounceMenu(); - UpdateExternalLinksMenu(); - UpdateFoldersMenu(); - UpdateToolsMenu(); - UpdateTrayMenu(); - UpdateViewMenu(); -} \ No newline at end of file diff --git a/monitor.cpp b/monitor.cpp deleted file mode 100644 index 0879f5d62..000000000 --- a/monitor.cpp +++ /dev/null @@ -1,363 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "monitor.h" - -#include "anime_db.h" -#include "anime_episode.h" -#include "common.h" -#include "logger.h" -#include "recognition.h" -#include "settings.h" -#include "string.h" - -#include "dlg/dlg_main.h" - -class FolderMonitor FolderMonitor; - -// ============================================================================= - -FolderInfo::FolderInfo() - : bytes_returned_(0), - directory_handle_(INVALID_HANDLE_VALUE), - notify_filter_(FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME), - state(MONITOR_STATE_STOPPED), - watch_subtree_(TRUE) { - ZeroMemory(&overlapped_, sizeof(overlapped_)); -} - -FolderInfo::~FolderInfo() { -} - -// ============================================================================= - -FolderMonitor::FolderMonitor() - : completion_port_(nullptr), - window_handle_(nullptr) { -} - -FolderMonitor::~FolderMonitor() { - Stop(); - ClearFolders(); - if (completion_port_) { - ::CloseHandle(completion_port_); - completion_port_ = nullptr; - } -} - -// ============================================================================= - -bool FolderMonitor::AddFolder(const wstring& folder) { - // Validate path - if (!FolderExists(folder)) { - return false; - } - - // Get directory handle - HANDLE handle = ::CreateFile(folder.c_str(), FILE_LIST_DIRECTORY, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, - OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr); - if (handle == INVALID_HANDLE_VALUE) { - return false; - } - - // Set folder data - folders_.resize(folders_.size() + 1); - folders_.back().directory_handle_ = handle; - folders_.back().path = folder; - - // Success - return true; -} - -bool FolderMonitor::ClearFolders() { - // Close handles - for (unsigned int i = 0; i < folders_.size(); i++) { - if (folders_[i].directory_handle_ != INVALID_HANDLE_VALUE) { - ::CloseHandle(folders_[i].directory_handle_); - folders_[i].directory_handle_ = INVALID_HANDLE_VALUE; - } - } - // Remove all items - folders_.clear(); - return true; -} - -bool FolderMonitor::Start() { - // Create worker thread - if (GetThreadHandle() == nullptr) { - CreateThread(nullptr, 0, 0); - } - - // Start watching folders - if (GetThreadHandle() != nullptr) { - for (unsigned int i = 0; i < folders_.size(); i++) { - completion_port_ = ::CreateIoCompletionPort(folders_[i].directory_handle_, - completion_port_, reinterpret_cast(&folders_[i]), 0); - if (completion_port_) { - ::PostQueuedCompletionStatus(completion_port_, sizeof(folders_[i]), - reinterpret_cast(&folders_[i]), &folders_[i].overlapped_); - } - } - } - - // Return - return GetThreadHandle() != nullptr; -} - -bool FolderMonitor::Stop() { - if (GetThreadHandle()) { - // Signal worker thread to stop - ::PostQueuedCompletionStatus(completion_port_, 0, 0, nullptr); - // Wait for thread to stop - ::WaitForSingleObject(GetThreadHandle(), INFINITE); - // Clean up - CloseThreadHandle(); - ::CloseHandle(completion_port_); - completion_port_ = nullptr; - } - return true; -} - -// ============================================================================= - -BOOL FolderMonitor::ReadDirectoryChanges(FolderInfo* folder_info) { - return ::ReadDirectoryChangesW( - folder_info->directory_handle_, - folder_info->buffer_, - MONITOR_BUFFER_SIZE, - folder_info->watch_subtree_, - folder_info->notify_filter_, - &folder_info->bytes_returned_, - &folder_info->overlapped_, - nullptr); -} - -DWORD FolderMonitor::ThreadProc() { - DWORD dwNumBytes = 0; - FolderInfo* folder_info = nullptr; - LPOVERLAPPED lpOverlapped; - - do { - // ... - ::GetQueuedCompletionStatus(GetCompletionPort(), &dwNumBytes, - reinterpret_cast(&folder_info), &lpOverlapped, INFINITE); - - if (folder_info) { - // Lock folder data - critical_section_.Enter(); - - switch (folder_info->state) { - // Start monitoring - case MONITOR_STATE_STOPPED: { - if (ReadDirectoryChanges(folder_info)) { - folder_info->state = MONITOR_STATE_ACTIVE; - LOG(LevelDebug, L"Started monitoring: " + folder_info->path); - } - break; - } - - // Change detected - case MONITOR_STATE_ACTIVE: { - DWORD dwNextEntryOffset = 0; - PFILE_NOTIFY_INFORMATION pfni = nullptr; - - do { - pfni = reinterpret_cast(folder_info->buffer_ + dwNextEntryOffset); - // Retrieve changed file name - WCHAR file_name[MAX_PATH + 1] = {'\0'}; - CopyMemory(file_name, pfni->FileName, pfni->FileNameLength); - // Add item to list - folder_info->change_list.resize(folder_info->change_list.size() + 1); - folder_info->change_list.back().action = pfni->Action; - folder_info->change_list.back().file_name = file_name; - // Continue to next change - dwNextEntryOffset += pfni->NextEntryOffset; - } while (pfni->NextEntryOffset != 0); - - // Send a message to the main thread - if (window_handle_) { - ::PostMessage(window_handle_, WM_MONITORCALLBACK, 0, reinterpret_cast(folder_info)); - } - - // Continue monitoring - ReadDirectoryChanges(folder_info); - break; - } - } - - // Unlock folder data - critical_section_.Leave(); - } - } while (folder_info); - - LOG(LevelDebug, L"Stopped monitoring."); - return 0; -} - -// ============================================================================= - -void FolderMonitor::Enable(bool enabled) { - // Stop monitoring - Stop(); - if (enabled) { - // Clear folder data - ClearFolders(); - // Add new folders - for (unsigned int i = 0; i < Settings.Folders.root.size(); i++) { - AddFolder(Settings.Folders.root[i]); - } - // Start monitoring again - Start(); - } -} - -void FolderMonitor::OnChange(FolderInfo* folder_info) { - // Lock folder data - critical_section_.Enter(); - - for (unsigned int i = 0; i < folder_info->change_list.size(); i++) { - #define LIST folder_info->change_list - - // Is path available? - bool path_available; - switch (folder_info->change_list[i].action) { - case FILE_ACTION_ADDED: - case FILE_ACTION_RENAMED_NEW_NAME: - path_available = true; - break; - case FILE_ACTION_REMOVED: - case FILE_ACTION_RENAMED_OLD_NAME: - path_available = false; - break; - default: - continue; - } - - anime::Episode episode; - AddTrailingSlash(folder_info->path); - wstring path = folder_info->path + LIST[i].file_name; - - // Is it a file or a folder? - bool is_folder = false; - if (path_available) { - is_folder = FolderExists(path); - } else { - is_folder = !ValidateFileExtension(GetFileExtension(LIST[i].file_name), 4); - } - - switch (folder_info->change_list[i].action) { - case FILE_ACTION_ADDED: - LOG(LevelDebug, L"Added: " + path); - break; - case FILE_ACTION_REMOVED: - LOG(LevelDebug, L"Removed: " + path); - break; - case FILE_ACTION_RENAMED_OLD_NAME: - LOG(LevelDebug, L"Renamed (old): " + path); - break; - case FILE_ACTION_RENAMED_NEW_NAME: - LOG(LevelDebug, L"Renamed (new): " + path); - break; - } - - // Compare with list item folders - if (is_folder && !path_available) { - int anime_id = 0; - for (auto it = AnimeDatabase.items.rbegin(); it != AnimeDatabase.items.rend(); ++it) { - if (!it->second.IsInList()) continue; - if (!it->second.GetFolder().empty() && IsEqual(it->second.GetFolder(), path)) { - anime_id = it->second.GetId(); - break; - } - } - if (anime_id > 0) { - if (i == LIST.size() - 1) { - LIST[i].anime_id = anime_id; - } else { - LIST[i + 1].anime_id = anime_id; - continue; - } - } - } - - // Change anime folder - if (is_folder && LIST[i].anime_id > 0) { - auto anime_item = AnimeDatabase.FindItem(LIST[i].anime_id); - anime_item->SetFolder(path_available ? path : L""); - Settings.Save(); - anime_item->CheckEpisodes(); - LOG(LevelDebug, L"Anime folder changed: " + anime_item->GetTitle()); - LOG(LevelDebug, L"Path: " + anime_item->GetFolder()); - continue; - } - - // Examine path and compare with list items - if (Meow.ExamineTitle(path, episode)) { - if (LIST[i].anime_id == 0 || is_folder == false) { - auto anime_item = Meow.MatchDatabase(episode, true, true, true, false, false); - if (anime_item) - LIST[i].anime_id = anime_item->GetId(); - } - if (LIST[i].anime_id > 0) { - auto anime_item = AnimeDatabase.FindItem(LIST[i].anime_id); - - // Set anime folder - if (path_available && anime_item->GetFolder().empty()) { - if (is_folder) { - anime_item->SetFolder(path); - Settings.Save(); - } else if (!episode.folder.empty()) { - anime::Episode temp_episode; - temp_episode.title = episode.folder; - if (Meow.CompareEpisode(temp_episode, *anime_item)) { - anime_item->SetFolder(episode.folder); - Settings.Save(); - anime_item->CheckEpisodes(); - } - } - if (!anime_item->GetFolder().empty()) { - LOG(LevelDebug, L"Anime folder changed: " + anime_item->GetTitle()); - LOG(LevelDebug, L"Path: " + anime_item->GetFolder()); - } - } - - // Set episode availability - if (!is_folder) { - int number = GetEpisodeHigh(episode.number); - int numberlow = GetEpisodeLow(episode.number); - for (int j = numberlow; j <= number; j++) { - if (anime_item->SetEpisodeAvailability(number, path_available, path)) { - LOG(LevelDebug, anime_item->GetTitle() + L" #" + ToWstr(j) + L" is " + - (path_available ? L"available." : L"unavailable.")); - } - } - } - } - } - #undef LIST - } - - // Clear change list - folder_info->change_list.clear(); - - // Unlock folder data - critical_section_.Leave(); -} \ No newline at end of file diff --git a/myanimelist.cpp b/myanimelist.cpp deleted file mode 100644 index 4289e0bdc..000000000 --- a/myanimelist.cpp +++ /dev/null @@ -1,620 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "myanimelist.h" - -#include "anime.h" -#include "anime_db.h" -#include "common.h" -#include "history.h" -#include "http.h" -#include "settings.h" -#include "string.h" -#include "taiga.h" -#include "time.h" -#include "xml.h" - -namespace mal { - -// ============================================================================= - -wstring GetUserPassEncoded() { - return Base64Encode(Settings.Account.MAL.user + L":" + - Settings.Account.MAL.password); -} - -// ============================================================================= - -bool AskToDiscuss(int anime_id, int episode_number) { - wstring data = - L"epNum=" + ToWstr(episode_number) + - L"&aid=" + ToWstr(anime_id) + - L"&id=" + ToWstr(anime_id); - - return Clients.service.list.Post( - L"myanimelist.net", - L"/includes/ajax.inc.php?t=50", - data, L"", - HTTP_MAL_AnimeAskToDiscuss, - anime_id); -} - -bool GetAnimeDetails(int anime_id) { - HttpClient* client; - if (anime_id > anime::ID_UNKNOWN) { - client = Clients.anime.GetClient(HTTP_Client_Search, anime_id); - } else { - client = &Clients.service.search; - } - - return client->Connect( - L"myanimelist.net", - L"/includes/ajax.inc.php?t=64&id=" + ToWstr(anime_id), - L"", L"GET", L"", L"myanimelist.net", L"", - HTTP_MAL_AnimeDetails, - anime_id); -} - -bool GetList() { - if (Settings.Account.MAL.user.empty()) - return false; - - return Clients.service.list.Connect( - L"myanimelist.net", - L"/malappinfo.php?u=" + Settings.Account.MAL.user + L"&status=all", - L"", L"GET", L"Accept-Encoding: gzip", L"", L"", - HTTP_MAL_RefreshList); -} - -bool Login() { - if (Taiga.logged_in || - Settings.Account.MAL.user.empty() || - Settings.Account.MAL.password.empty()) - return false; - - wstring header = Clients.service.list.GetDefaultHeader() + - L"Authorization: Basic " + GetUserPassEncoded(); - - return Clients.service.list.Connect( - L"myanimelist.net", - L"/api/account/verify_credentials.xml", - L"", L"GET", header, L"myanimelist.net", L"", - HTTP_MAL_Login); -} - -bool DownloadImage(int anime_id, const wstring& image_url) { - if (image_url.empty()) - return false; - - HttpClient* client; - if (anime_id > anime::ID_UNKNOWN) { - client = Clients.anime.GetClient(HTTP_Client_Image, anime_id); - } else { - client = &Clients.service.image; - } - - return client->Get(win32::Url(image_url), - anime::GetImagePath(anime_id), - HTTP_MAL_Image, - anime_id); -} - -bool DownloadUserImage(bool thumb) { - if (!AnimeDatabase.user.GetId()) - return false; - - win32::Url url; - wstring path = Taiga.GetDataPath() + L"user\\" + - AnimeDatabase.user.GetName() + L"\\"; - - if (thumb) { - url.Crack(L"http://myanimelist.net/images/userimages/thumbs/" + - ToWstr(AnimeDatabase.user.GetId()) + L"_thumb.jpg"); - path += ToWstr(AnimeDatabase.user.GetId()) + L"_thumb.jpg"; - } else { - url.Crack(L"http://cdn.myanimelist.net/images/userimages/" + - ToWstr(AnimeDatabase.user.GetId()) + L".jpg"); - path += ToWstr(AnimeDatabase.user.GetId()) + L".jpg"; - } - - return Clients.service.image.Get(url, path, HTTP_MAL_UserImage); -} - -bool ParseAnimeDetails(const wstring& data) { - if (data.empty()) - return false; - - int anime_id = ToInt(InStr(data, L"myanimelist.net/anime/", L"/")); - if (!anime_id) - return false; - - anime::Item anime_item; - anime_item.SetId(anime_id); - anime_item.SetGenres(InStr(data, L"Genres: ", L"
")); - anime_item.SetRank(InStr(data, L"Ranked: ", L"
")); - anime_item.SetPopularity(InStr(data, L"Popularity: ", L"
")); - wstring score = InStr(data, L"Score: ", L"
"); - StripHtmlTags(score); - anime_item.SetScore(score); - anime_item.last_modified = time(nullptr); - AnimeDatabase.UpdateItem(anime_item); - - return true; -} - -bool ParseSearchResult(const wstring& data, int anime_id) { - if (data.empty()) - return false; - - bool found_item = false; - - xml_document doc; - xml_parse_result result = doc.load(data.c_str()); - - if (result.status == status_ok) { - xml_node node = doc.child(L"anime"); - for (xml_node entry = node.child(L"entry"); entry; entry = entry.next_sibling(L"entry")) { - auto current_anime_item = AnimeDatabase.FindItem(anime_id); - - anime::Item anime_item; - anime_item.SetId(XML_ReadIntValue(entry, L"id")); - if (!current_anime_item || !current_anime_item->keep_title) - anime_item.SetTitle(mal::DecodeText(XML_ReadStrValue(entry, L"title"))); - anime_item.SetEnglishTitle(mal::DecodeText(XML_ReadStrValue(entry, L"english"))); - anime_item.SetSynonyms(mal::DecodeText(XML_ReadStrValue(entry, L"synonyms"))); - anime_item.SetEpisodeCount(XML_ReadIntValue(entry, L"episodes")); - anime_item.SetScore(XML_ReadStrValue(entry, L"score")); - anime_item.SetType(mal::TranslateType(XML_ReadStrValue(entry, L"type"))); - anime_item.SetAiringStatus(mal::TranslateStatus(XML_ReadStrValue(entry, L"status"))); - anime_item.SetDate(anime::DATE_START, XML_ReadStrValue(entry, L"start_date")); - anime_item.SetDate(anime::DATE_END, XML_ReadStrValue(entry, L"end_date")); - wstring synopsis = mal::DecodeText(XML_ReadStrValue(entry, L"synopsis")); - if (!StartsWith(synopsis, L"No synopsis has been added for this series yet.")) - anime_item.SetSynopsis(synopsis); - anime_item.SetImageUrl(XML_ReadStrValue(entry, L"image")); - anime_item.last_modified = time(nullptr); - AnimeDatabase.UpdateItem(anime_item); - - if (!anime_id || anime_id == anime_item.GetId()) - found_item = true; - } - } - - return found_item; -} - -bool SearchAnime(int anime_id, wstring title) { - HttpClient* client; - if (anime_id > anime::ID_UNKNOWN) { - client = Clients.anime.GetClient(HTTP_Client_Search, anime_id); - } else { - client = &Clients.service.search; - } - - if (title.empty()) { - auto anime_item = AnimeDatabase.FindItem(anime_id); - title = anime_item->GetTitle(); - } - - Replace(title, L"+", L"%2B", true); - ReplaceChar(title, L'\u2729', L'\u2606'); // stress outlined white star to white star - - if (Settings.Account.MAL.user.empty() || - Settings.Account.MAL.password.empty()) - return false; - - wstring header = client->GetDefaultHeader() + - L"Authorization: Basic " + GetUserPassEncoded(); - - return client->Connect( - L"myanimelist.net", - L"/api/anime/search.xml?q=" + title, - L"", L"GET", header, L"myanimelist.net", L"", - HTTP_MAL_SearchAnime, - anime_id); -} - -bool Update(AnimeValues& anime_values, int anime_id, int update_mode) { - auto anime_item = AnimeDatabase.FindItem(anime_id); - if (!anime_item) - return false; - - #define ADD_DATA_N(name, value) \ - if (anime_values.value) \ - data += L"\r\n\t<" ##name L">" + ToWstr(*anime_values.value) + L""; - #define ADD_DATA_S(name, value) \ - if (anime_values.value) \ - data += L"\r\n\t<" ##name L">" + *anime_values.value + L""; - - // Build XML data - wstring data; - if (update_mode != HTTP_MAL_AnimeDelete) { - data = L"data=\r\n"; - ADD_DATA_N(L"episode", episode); - ADD_DATA_N(L"status", status); - ADD_DATA_N(L"score", score); - ADD_DATA_N(L"downloaded_episodes", downloaded_episodes); - ADD_DATA_N(L"storage_type", storage_type); - ADD_DATA_N(L"storage_value", storage_value); - ADD_DATA_N(L"times_rewatched", times_rewatched); - ADD_DATA_N(L"rewatch_value", rewatch_value); - ADD_DATA_S(L"date_start", date_start); - ADD_DATA_S(L"date_finish", date_finish); - ADD_DATA_N(L"priority", priority); - ADD_DATA_N(L"enable_discussion", enable_discussion); - ADD_DATA_N(L"enable_rewatching", enable_rewatching); - ADD_DATA_S(L"comments", comments); - ADD_DATA_S(L"fansub_group", fansub_group); - ADD_DATA_S(L"tags", tags); - data += L"\r\n"; - } - - #undef ADD_DATA_N - #undef ADD_DATA_S - #undef ANIME - - win32::Url url; - switch (update_mode) { - // Add anime - case HTTP_MAL_AnimeAdd: - url = L"myanimelist.net/api/animelist/add/" + ToWstr(anime_id) + L".xml"; - break; - // Delete anime - case HTTP_MAL_AnimeDelete: - url = L"myanimelist.net/api/animelist/delete/" + ToWstr(anime_id) + L".xml"; - break; - // Update anime - default: - url = L"myanimelist.net/api/animelist/update/" + ToWstr(anime_id) + L".xml"; - break; - } - - wstring header = Clients.service.list.GetDefaultHeader() + - L"Authorization: Basic " + GetUserPassEncoded(); - - Clients.service.list.Connect( - url, data, L"POST", header, L"myanimelist.net", L"", - update_mode, static_cast(anime_id)); - - return true; -} - -bool UpdateSucceeded(EventItem& item, const wstring& data, int status_code) { - int update_mode = item.mode; - bool success = false; - - switch (update_mode) { - case HTTP_MAL_AnimeAdd: - success = IsNumeric(data) || - InStr(data, L"This anime is already on your list") > -1 || - InStr(data, L"201 Created") > -1; // TODO: Remove when MAL fixes its API - break; - case HTTP_MAL_AnimeDelete: - success = data == L"Deleted"; - break; - default: - success = data == L"Updated"; - break; - } - - if (success) - return true; - - // Set error message on failure - item.reason = data; - Replace(item.reason, L"
", L"\r\n"); - StripHtmlTags(item.reason); - - return false; -} - -// ============================================================================= - -wstring DecodeText(wstring text) { - Replace(text, L"
", L"\r", true); - Replace(text, L"\n\n", L"\r\n\r\n", true); - - StripHtmlTags(text); - DecodeHtmlEntities(text); - - return text; -} - -bool IsValidDate(const Date& date) { - return date.year > 0; -} - -bool IsValidDate(const wstring& date) { - return date.length() == 10 && !StartsWith(date, L"0000"); -} - -bool IsValidEpisode(int episode, int watched, int total) { - if ((episode < 0) || - (episode < watched) || - (episode == watched && total != 1) || - (episode > total && total != 0)) - return false; - - return true; -} - -Date ParseDateString(const wstring& str) { - Date date; - - if (IsValidDate(str)) { - date.year = ToInt(str.substr(0, 4)); - date.month = ToInt(str.substr(5, 2)); - date.day = ToInt(str.substr(8, 2)); - } - - return date; -} - -void GetSeasonInterval(const wstring& season, Date& date_start, Date& date_end) { - std::map> interval; - interval[L"Spring"] = std::make_pair(3, 5); - interval[L"Summer"] = std::make_pair(6, 8); - interval[L"Fall"] = std::make_pair(9, 11); - interval[L"Winter"] = std::make_pair(12, 2); - - const int days_in_months[] = - {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - - vector season_year; - Split(season, L" ", season_year); - - date_start.year = ToInt(season_year.at(1)); - date_end.year = ToInt(season_year.at(1)); - if (season_year.at(0) == L"Winter") date_start.year--; - date_start.month = interval[season_year.at(0)].first; - date_end.month = interval[season_year.at(0)].second; - date_start.day = 1; - date_end.day = days_in_months[date_end.month - 1]; -} - -// ============================================================================= - -wstring TranslateDate(const Date& date) { - if (!mal::IsValidDate(date)) - return L"?"; - - wstring result; - - if (date.month > 0 && date.month <= 12) { - const wchar_t* months[] = { - L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", - L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" - }; - result += months[date.month - 1]; - result += L" "; - } - if (date.day > 0) - result += ToWstr(date.day) + L", "; - result += ToWstr(date.year); - - return result; -} - -wstring TranslateDateForApi(const Date& date) { - if (!mal::IsValidDate(date)) - return L""; - - return PadChar(ToWstr(date.month), '0', 2) + // MM - PadChar(ToWstr(date.day), '0', 2) + // DD - PadChar(ToWstr(date.year), '0', 4); // YYYY -} -Date TranslateDateFromApi(const wstring& date) { - if (date.size() != 8) - return Date(); - - return Date(ToInt(date.substr(4, 4)), - ToInt(date.substr(0, 2)), - ToInt(date.substr(2, 2))); -} - -wstring TranslateDateToSeason(const Date& date) { - if (!mal::IsValidDate(date)) - return L"Unknown"; - - wstring season; - unsigned short year = date.year; - - if (date.month == 0) { - season = L"Unknown"; - } else if (date.month < 3) { // Jan-Feb - season = L"Winter"; - } else if (date.month < 6) { // Mar-May - season = L"Spring"; - } else if (date.month < 9) { // Jun-Aug - season = L"Summer"; - } else if (date.month < 12) { // Sep-Nov - season = L"Fall"; - } else { // Dec - season = L"Winter"; - year++; - } - - return season + L" " + ToWstr(year); -} - -wstring TranslateSeasonToMonths(const wstring& season) { - const wchar_t* months[] = { - L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", - L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" - }; - - Date date_start, date_end; - GetSeasonInterval(season, date_start, date_end); - - wstring result = months[date_start.month - 1]; - result += L" " + ToWstr(date_start.year) + L" to "; - result += months[date_end.month - 1]; - result += L" " + ToWstr(date_end.year); - - return result; -} - -wstring TranslateMyStatus(int value, bool add_count) { - #define ADD_COUNT() (add_count ? L" (" + ToWstr(AnimeDatabase.user.GetItemCount(value)) + L")" : L"") - switch (value) { - case mal::MYSTATUS_NOTINLIST: return L"Not in list"; - case mal::MYSTATUS_WATCHING: return L"Currently watching" + ADD_COUNT(); - case mal::MYSTATUS_COMPLETED: return L"Completed" + ADD_COUNT(); - case mal::MYSTATUS_ONHOLD: return L"On hold" + ADD_COUNT(); - case mal::MYSTATUS_DROPPED: return L"Dropped" + ADD_COUNT(); - case mal::MYSTATUS_PLANTOWATCH: return L"Plan to watch" + ADD_COUNT(); - default: return L""; - } - #undef ADD_COUNT -} -int TranslateMyStatus(const wstring& value) { - if (IsEqual(value, L"Currently watching")) { - return mal::MYSTATUS_WATCHING; - } else if (IsEqual(value, L"Completed")) { - return mal::MYSTATUS_COMPLETED; - } else if (IsEqual(value, L"On hold")) { - return mal::MYSTATUS_ONHOLD; - } else if (IsEqual(value, L"Dropped")) { - return mal::MYSTATUS_DROPPED; - } else if (IsEqual(value, L"Plan to watch")) { - return mal::MYSTATUS_PLANTOWATCH; - } else { - return 0; - } -} - -wstring TranslateNumber(int value, const wstring& default_char) { - return value > 0 ? ToWstr(value) : default_char; -} - -wstring TranslateRewatchValue(int value) { - switch (value) { - case mal::REWATCH_VERYLOW: return L"Very low"; - case mal::REWATCH_LOW: return L"Low"; - case mal::REWATCH_MEDIUM: return L"Medium"; - case mal::REWATCH_HIGH: return L"High"; - case mal::REWATCH_VERYHIGH: return L"Very high"; - default: return L""; - } -} - -wstring TranslateStatus(int value) { - switch (value) { - case mal::STATUS_AIRING: return L"Currently airing"; - case mal::STATUS_FINISHED: return L"Finished airing"; - case mal::STATUS_NOTYETAIRED: return L"Not yet aired"; - default: return ToWstr(value); - } -} -int TranslateStatus(const wstring& value) { - if (IsEqual(value, L"Currently airing")) { - return mal::STATUS_AIRING; - } else if (IsEqual(value, L"Finished airing")) { - return mal::STATUS_FINISHED; - } else if (IsEqual(value, L"Not yet aired")) { - return mal::STATUS_NOTYETAIRED; - } else { - return 0; - } -} - -wstring TranslateStorageType(int value) { - switch (value) { - case mal::STORAGE_HARDDRIVE: return L"Hard drive"; - case mal::STORAGE_DVDCD: return L"DVD/CD"; - case mal::STORAGE_NONE: return L"None"; - case mal::STORAGE_RETAILDVD: return L"Retail DVD"; - case mal::STORAGE_VHS: return L"VHS"; - case mal::STORAGE_EXTERNALHD: return L"External HD"; - case mal::STORAGE_NAS: return L"NAS"; - default: return L""; - } -} - -wstring TranslateType(int value) { - switch (value) { - case mal::TYPE_TV: return L"TV"; - case mal::TYPE_OVA: return L"OVA"; - case mal::TYPE_MOVIE: return L"Movie"; - case mal::TYPE_SPECIAL: return L"Special"; - case mal::TYPE_ONA: return L"ONA"; - case mal::TYPE_MUSIC: return L"Music"; - default: return L""; - } -} -int TranslateType(const wstring& value) { - if (IsEqual(value, L"TV")) { - return mal::TYPE_TV; - } else if (IsEqual(value, L"OVA")) { - return mal::TYPE_OVA; - } else if (IsEqual(value, L"Movie")) { - return mal::TYPE_MOVIE; - } else if (IsEqual(value, L"Special")) { - return mal::TYPE_SPECIAL; - } else if (IsEqual(value, L"ONA")) { - return mal::TYPE_ONA; - } else if (IsEqual(value, L"Music")) { - return mal::TYPE_MUSIC; - } else { - return 0; - } -} - -// ============================================================================= - -void ViewAnimePage(int anime_id) { - ExecuteLink(L"http://myanimelist.net/anime/" + ToWstr(anime_id) + L"/"); -} - -void ViewAnimeSearch(const wstring& title) { - ExecuteLink(L"http://myanimelist.net/anime.php?q=" + title + L"&referer=" + APP_NAME); -} - -void ViewHistory() { - ExecuteLink(L"http://myanimelist.net/history/" + Settings.Account.MAL.user); -} - -void ViewMessages() { - ExecuteLink(L"http://myanimelist.net/mymessages.php"); -} - -void ViewPanel() { - ExecuteLink(L"http://myanimelist.net/panel.php"); -} - -void ViewProfile() { - ExecuteLink(L"http://myanimelist.net/profile/" + Settings.Account.MAL.user); -} - -void ViewSeasonGroup() { - ExecuteLink(L"http://myanimelist.net/clubs.php?cid=743"); -} - -void ViewUpcomingAnime() { - Date date = GetDate(); - - ExecuteLink(L"http://myanimelist.net/anime.php" - L"?sd=" + ToWstr(date.day) + - L"&sm=" + ToWstr(date.month) + - L"&sy=" + ToWstr(date.year) + - L"&em=0&ed=0&ey=0&o=2&w=&c[]=a&c[]=d&cv=1"); -} - -} // namespace mal \ No newline at end of file diff --git a/myanimelist.h b/myanimelist.h deleted file mode 100644 index c00546144..000000000 --- a/myanimelist.h +++ /dev/null @@ -1,148 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef MYANIMELIST_H -#define MYANIMELIST_H - -#include "std.h" -#include "optional.h" - -namespace anime { -class ListItem; -} -class Date; -class EventItem; - -namespace mal { - -// ============================================================================= - -enum MyStatus { - MYSTATUS_NOTINLIST, - MYSTATUS_WATCHING, - MYSTATUS_COMPLETED, - MYSTATUS_ONHOLD, - MYSTATUS_DROPPED, - MYSTATUS_UNKNOWN, - MYSTATUS_PLANTOWATCH -}; - -enum RewatchValue { - REWATCH_VERYLOW = 1, - REWATCH_LOW, - REWATCH_MEDIUM, - REWATCH_HIGH, - REWATCH_VERYHIGH -}; - -enum Status { - STATUS_UNKNOWN, - STATUS_AIRING, - STATUS_FINISHED, - STATUS_NOTYETAIRED -}; - -enum StorageType { - STORAGE_HARDDRIVE = 1, - STORAGE_DVDCD, - STORAGE_NONE, - STORAGE_RETAILDVD, - STORAGE_VHS, - STORAGE_EXTERNALHD, - STORAGE_NAS -}; - -enum Type { - TYPE_UNKNOWN, - TYPE_TV, - TYPE_OVA, - TYPE_MOVIE, - TYPE_SPECIAL, - TYPE_ONA, - TYPE_MUSIC -}; - -class AnimeValues { -public: - Optional episode; - Optional status; - Optional score; - Optional downloaded_episodes; - Optional storage_type; - Optional storage_value; - Optional times_rewatched; - Optional rewatch_value; - Optional date_start; - Optional date_finish; - Optional priority; - Optional enable_discussion; - Optional enable_rewatching; - Optional comments; - Optional fansub_group; - Optional tags; -}; - -// ============================================================================= - -bool AskToDiscuss(int anime_id, int episode_number); -bool DownloadImage(int anime_id, const wstring& image_url); -bool DownloadUserImage(bool thumb); -bool GetAnimeDetails(int anime_id); -bool GetList(); -bool Login(); -bool ParseAnimeDetails(const wstring& data); -bool ParseSearchResult(const wstring& data, int anime_id = 0); -bool SearchAnime(int anime_id, wstring title); -bool Update(AnimeValues& anime_values, int anime_id, int update_mode); -bool UpdateSucceeded(EventItem& item, const wstring& data, int status_code); - -wstring DecodeText(wstring text); -bool IsValidDate(const Date& date); -bool IsValidDate(const wstring& date); -bool IsValidEpisode(int episode, int watched, int total); -Date ParseDateString(const wstring& str); -void GetSeasonInterval(const wstring& season, Date& date_start, Date& date_end); - -wstring TranslateDate(const Date& date); -wstring TranslateDateForApi(const Date& date); -Date TranslateDateFromApi(const wstring& date); -wstring TranslateDateToSeason(const Date& date); -wstring TranslateSeasonToMonths(const wstring& season); -wstring TranslateMyStatus(int value, bool add_count); -wstring TranslateNumber(int value, const wstring& default_char = L"-"); -wstring TranslateRewatchValue(int value); -wstring TranslateStatus(int value); -wstring TranslateStorageType(int value); -wstring TranslateType(int value); - -int TranslateMyStatus(const wstring& value); -int TranslateStatus(const wstring& value); -int TranslateType(const wstring& value); - -void ViewAnimePage(int anime_id); -void ViewAnimeSearch(const wstring& title); -void ViewHistory(); -void ViewMessages(); -void ViewPanel(); -void ViewProfile(); -void ViewSeasonGroup(); -void ViewUpcomingAnime(); - -} // namespace mal - -#endif // MYANIMELIST_H \ No newline at end of file diff --git a/Taiga.sln b/project/vs2013/Taiga.sln similarity index 75% rename from Taiga.sln rename to project/vs2013/Taiga.sln index 1e0656ab5..29b502bf8 100644 --- a/Taiga.sln +++ b/project/vs2013/Taiga.sln @@ -1,24 +1,21 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Taiga", "Taiga.vcxproj", "{50BAD968-CEBF-46CA-A18A-FE3E8D625F94}" -EndProject -Global - GlobalSection(SubversionScc) = preSolution - Svn-Managed = True - Manager = AnkhSVN - Subversion Support for Visual Studio - EndGlobalSection - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {50BAD968-CEBF-46CA-A18A-FE3E8D625F94}.Debug|Win32.ActiveCfg = Debug|Win32 - {50BAD968-CEBF-46CA-A18A-FE3E8D625F94}.Debug|Win32.Build.0 = Debug|Win32 - {50BAD968-CEBF-46CA-A18A-FE3E8D625F94}.Release|Win32.ActiveCfg = Release|Win32 - {50BAD968-CEBF-46CA-A18A-FE3E8D625F94}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Taiga", "Taiga.vcxproj", "{50BAD968-CEBF-46CA-A18A-FE3E8D625F94}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {50BAD968-CEBF-46CA-A18A-FE3E8D625F94}.Debug|Win32.ActiveCfg = Debug|Win32 + {50BAD968-CEBF-46CA-A18A-FE3E8D625F94}.Debug|Win32.Build.0 = Debug|Win32 + {50BAD968-CEBF-46CA-A18A-FE3E8D625F94}.Release|Win32.ActiveCfg = Release|Win32 + {50BAD968-CEBF-46CA-A18A-FE3E8D625F94}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/project/vs2013/Taiga.vcxproj b/project/vs2013/Taiga.vcxproj new file mode 100644 index 000000000..650bfda3d --- /dev/null +++ b/project/vs2013/Taiga.vcxproj @@ -0,0 +1,386 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {50BAD968-CEBF-46CA-A18A-FE3E8D625F94} + Win32Proj + Taiga + + + + Application + true + Unicode + v120_xp + + + Application + false + true + Unicode + v120_xp + + + + + + + + + + + + + true + ..\..\bin\$(Configuration)\ + ..\..\build\$(Configuration)\ + $(ProjectDir);$(IncludePath) + + + false + ..\..\bin\$(Configuration)\ + ..\..\build\$(Configuration)\ + $(ProjectDir);$(IncludePath) + + + + + + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) + $(IntDir)\x\x\%(RelativeDir) + ..\..\deps\src;..\..\src + MultiThreadedDebug + + + Windows + true + comctl32.lib;libcurl_a_debug.lib;Oleacc.lib;psapi.lib;shlwapi.lib;uxtheme.lib;Winmm.lib;%(AdditionalDependencies) + comctl32.dll + type=%27win32%27 name=%27Microsoft.Windows.Common-Controls%27 version=%276.0.0.0%27 processorArchitecture=%27x86%27 publicKeyToken=%276595b64144ccf1df%27 language=%27*%27;%(AdditionalManifestDependencies) + ..\..\deps\lib;%(AdditionalLibraryDirectories) + + + + + Level3 + + + MinSpace + true + true + WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) + true + MultiThreaded + $(IntDir)\x\x\%(RelativeDir) + ..\..\deps\src;..\..\src + + + Windows + true + true + true + comctl32.lib;libcurl_a.lib;Oleacc.lib;psapi.lib;shlwapi.lib;uxtheme.lib;Winmm.lib;%(AdditionalDependencies) + comctl32.dll + type=%27win32%27 name=%27Microsoft.Windows.Common-Controls%27 version=%276.0.0.0%27 processorArchitecture=%27x86%27 publicKeyToken=%276595b64144ccf1df%27 language=%27*%27;%(AdditionalManifestDependencies) + AsInvoker + ..\..\deps\lib;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/project/vs2013/Taiga.vcxproj.filters b/project/vs2013/Taiga.vcxproj.filters new file mode 100644 index 000000000..e8feda806 --- /dev/null +++ b/project/vs2013/Taiga.vcxproj.filters @@ -0,0 +1,890 @@ + + + + + {7445b39f-ce54-4d5d-8b95-9a45f6138c5f} + + + {58598ee5-0814-4581-87d0-a28008a25893} + + + {9fac04ef-29f0-4083-a1a7-f20d503a40df} + + + {ff7ccb38-f620-487f-b0de-e4d23f544d34} + + + {4ebea91d-8158-4349-80bf-04399e1f6356} + + + {94d61ae9-9528-4633-875c-8a84df33f677} + + + {aad11cb9-6941-4934-9c9a-6523a6da7d06} + + + {5802bd8b-5cfa-4945-b7eb-c52aa2fbef32} + + + {e10ab21b-a2bf-4ffc-9e68-a2bfb8ab3ee4} + + + {73658da5-e312-4fe4-85e6-38a578acf869} + + + {dd9648e9-7e26-4239-a931-8fee59d183e7} + + + {0f915cf6-1a44-47c9-8c1f-3a640aa5fb67} + + + {1dd6ca23-29e1-4a1b-b1fb-a3b4988da7b0} + + + {d6043b6b-33fb-470e-bab4-28380104489e} + + + {4eb462d7-7759-463c-99cf-e34daf381970} + + + {bf5a6cc7-a783-4694-9bcf-49c694959131} + + + {1fde5dda-2f86-4de5-a8cc-da7664cc759b} + + + {cda71bda-a7b7-46d6-9c94-a7fafbabba09} + + + + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + deps\base64 + + + deps\jsoncpp + + + deps\jsoncpp + + + deps\jsoncpp + + + deps\pugixml + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + library + + + library + + + library + + + library + + + library\anime + + + library\anime + + + library\anime + + + library\anime + + + library\anime + + + library\anime + + + library\anime + + + sync + + + sync + + + sync + + + sync\hummingbird + + + sync\hummingbird + + + sync\myanimelist + + + sync\myanimelist + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + track + + + track + + + track + + + track + + + track + + + track + + + track + + + ui + + + ui + + + ui + + + ui + + + ui + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + win + + + win + + + win + + + win + + + win + + + win + + + win + + + win + + + win + + + win + + + win + + + win + + + win + + + win\ctrl + + + win\ctrl + + + win\ctrl + + + win\ctrl + + + win\ctrl + + + win\ctrl + + + win\ctrl + + + win\ctrl + + + win\ctrl + + + win\ctrl + + + win\ctrl + + + win\ctrl + + + win\ctrl + + + win\ctrl + + + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + base + + + deps\base64 + + + deps\curl + + + deps\curl + + + deps\curl + + + deps\curl + + + deps\curl + + + deps\curl + + + deps\curl + + + deps\curl + + + deps\curl + + + deps\jsoncpp + + + deps\jsoncpp + + + deps\jsoncpp + + + deps\jsoncpp + + + deps\jsoncpp + + + deps\jsoncpp + + + deps\jsoncpp + + + deps\jsoncpp + + + deps\jsoncpp + + + deps\jsoncpp + + + deps\pugixml + + + deps\pugixml + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + deps\zlib + + + library + + + library + + + library + + + library + + + library\anime + + + library\anime + + + library\anime + + + library\anime + + + library\anime + + + library\anime + + + sync + + + sync + + + sync + + + sync\hummingbird + + + sync\hummingbird + + + sync\hummingbird + + + sync\myanimelist + + + sync\myanimelist + + + sync\myanimelist + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + taiga + + + track + + + track + + + track + + + track + + + track + + + track + + + ui + + + ui + + + ui + + + ui + + + ui + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + ui\dlg + + + win + + + win + + + win + + + win + + + win + + + win + + + win + + + win + + + win + + + win + + + win + + + win + + + win + + + win\ctrl + + + + + deps\jsoncpp + + + deps\jsoncpp + + + deps\jsoncpp + + + + + taiga + + + taiga + + + \ No newline at end of file diff --git a/recognition.h b/recognition.h deleted file mode 100644 index 56a5d4b41..000000000 --- a/recognition.h +++ /dev/null @@ -1,106 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef RECOGNITION_H -#define RECOGNITION_H - -#include "std.h" - -namespace anime { -class Episode; -class Item; -} -class Token; - -// ============================================================================= - -class RecognitionEngine { -public: - RecognitionEngine(); - virtual ~RecognitionEngine() {}; - - void Initialize(); - - anime::Item* MatchDatabase(anime::Episode& episode, - bool in_list = true, - bool reverse = true, - bool strict = true, - bool check_episode = true, - bool check_date = true, - bool give_score = false); - - bool CompareEpisode(anime::Episode& episode, - const anime::Item& anime_item, - bool strict = true, - bool check_episode = true, - bool check_date = true, - bool give_score = false); - - bool ExamineTitle(wstring title, - anime::Episode& episode, - bool examine_inside = true, - bool examine_outside = true, - bool examine_number = true, - bool check_extras = true, - bool check_extension = true); - - void ExamineToken(Token& token, anime::Episode& episode, bool compare_extras); - - void CleanTitle(wstring& title); - void UpdateCleanTitles(int anime_id); - - std::multimap> GetScores(); - - // Mapped as - std::map scores; - - std::map> clean_titles; - - vector audio_keywords; - vector video_keywords; - vector extra_keywords; - vector extra_unsafe_keywords; - vector version_keywords; - vector valid_extensions; - vector episode_keywords; - vector episode_prefixes; - - private: - bool CompareTitle(const wstring& anime_title, - anime::Episode& episode, - const anime::Item& anime_item, - bool strict = true); - bool ScoreTitle(const anime::Episode& episode, - const anime::Item& anime_item); - - void AppendKeyword(wstring& str, const wstring& keyword); - bool CompareKeys(const wstring& str, const vector& keys); - void EraseUnnecessary(wstring& str); - void TransliterateSpecial(wstring& str); - bool IsEpisodeFormat(const wstring& str, anime::Episode& episode, const wchar_t separator = ' '); - bool IsResolution(const wstring& str); - bool IsCountingWord(const wstring& str); - bool IsTokenEnclosed(const Token& token); - void ReadKeyword(unsigned int id, vector& str); - size_t TokenizeTitle(const wstring& str, const wstring& delimiters, vector& tokens); - bool ValidateEpisodeNumber(anime::Episode& episode); -}; - -extern RecognitionEngine Meow; - -#endif // RECOGNITION_H \ No newline at end of file diff --git a/res/menu.xml b/res/menu.xml index 03d84f75e..01352d871 100644 --- a/res/menu.xml +++ b/res/menu.xml @@ -3,7 +3,7 @@ - + @@ -18,18 +18,28 @@ - - + + - - - + + + + + + + + + + + + + + - @@ -46,13 +56,7 @@ - - - - - - - + - @@ -74,6 +78,7 @@ + @@ -89,7 +94,7 @@ - + @@ -165,8 +170,8 @@ - - + + @@ -235,7 +240,7 @@ - + @@ -251,7 +256,7 @@ - + @@ -320,6 +325,7 @@ + diff --git a/resource.h b/resource.h deleted file mode 100644 index ee6d173c4..000000000 --- a/resource.h +++ /dev/null @@ -1,213 +0,0 @@ -#ifndef IDC_STATIC -#define IDC_STATIC (-1) -#endif - -#define IDI_MAIN 101 -#define IDD_ABOUT 110 -#define IDD_ANIME_INFO 111 -#define IDD_ANIME_INFO_PAGE01 112 -#define IDD_ANIME_INFO_PAGE02 113 -#define IDD_ANIME_LIST 114 -#define IDD_FEED_CONDITION 115 -#define IDD_FEED_FILTER 116 -#define IDD_FEED_FILTER_PAGE1 117 -#define IDD_FEED_FILTER_PAGE2 118 -#define IDD_FEED_FILTER_PAGE3 119 -#define IDD_FORMAT 120 -#define IDD_HISTORY 121 -#define IDD_INPUT 122 -#define IDD_MAIN 123 -#define IDD_SEARCH 124 -#define IDD_SEASON 125 -#define IDD_SETTINGS 126 -#define IDD_SETTINGS_APP_BEHAVIOR 127 -#define IDD_SETTINGS_APP_CONNECTION 128 -#define IDD_SETTINGS_APP_INTERFACE 129 -#define IDD_SETTINGS_APP_LIST 130 -#define IDD_SETTINGS_LIBRARY_CACHE 131 -#define IDD_SETTINGS_LIBRARY_FOLDERS 132 -#define IDD_SETTINGS_RECOGNITION_GENERAL 133 -#define IDD_SETTINGS_RECOGNITION_MEDIA 134 -#define IDD_SETTINGS_RECOGNITION_STREAM 135 -#define IDD_SETTINGS_SERVICES_MAL 136 -#define IDD_SETTINGS_SHARING_HTTP 137 -#define IDD_SETTINGS_SHARING_MESSENGER 138 -#define IDD_SETTINGS_SHARING_MIRC 139 -#define IDD_SETTINGS_SHARING_SKYPE 140 -#define IDD_SETTINGS_SHARING_TWITTER 141 -#define IDD_SETTINGS_TORRENTS_DISCOVERY 142 -#define IDD_SETTINGS_TORRENTS_DOWNLOADS 143 -#define IDD_SETTINGS_TORRENTS_FILTERS 144 -#define IDD_STATS 145 -#define IDD_TEST_RECOGNITION 146 -#define IDD_TORRENT 147 -#define IDD_UPDATE 148 -#define IDD_UPDATE_NEW 149 -#define IDC_BUTTON_ADDFOLDER 1000 -#define IDC_BUTTON_BROWSE 1001 -#define IDC_BUTTON_CACHE_CLEAR 1002 -#define IDC_BUTTON_CANCELSEARCH 1003 -#define IDC_BUTTON_FORMAT_BALLOON 1004 -#define IDC_BUTTON_FORMAT_HTTP 1005 -#define IDC_BUTTON_FORMAT_MIRC 1006 -#define IDC_BUTTON_FORMAT_MSN 1007 -#define IDC_BUTTON_FORMAT_SKYPE 1008 -#define IDC_BUTTON_FORMAT_TWITTER 1009 -#define IDC_BUTTON_MIRC_TEST 1010 -#define IDC_BUTTON_REMOVEFOLDER 1011 -#define IDC_BUTTON_TORRENT_BROWSE_APP 1012 -#define IDC_BUTTON_TORRENT_BROWSE_FOLDER 1013 -#define IDC_BUTTON_TWITTER_AUTH 1014 -#define IDC_CHECK_ANIME_ALT 1015 -#define IDC_CHECK_ANIME_REWATCH 1016 -#define IDC_CHECK_AUTOSTART 1017 -#define IDC_CHECK_CACHE1 1018 -#define IDC_CHECK_CACHE2 1019 -#define IDC_CHECK_CACHE3 1020 -#define IDC_CHECK_FOLDERS_WATCH 1021 -#define IDC_CHECK_GENERAL_CLOSE 1022 -#define IDC_CHECK_GENERAL_MINIMIZE 1023 -#define IDC_CHECK_HIGHLIGHT 1024 -#define IDC_CHECK_HTTP 1025 -#define IDC_CHECK_LIST_ENGLISH 1026 -#define IDC_CHECK_LIST_PROGRESS_AIRED 1027 -#define IDC_CHECK_LIST_PROGRESS_AVAILABLE 1028 -#define IDC_CHECK_MESSENGER 1029 -#define IDC_CHECK_MIRC 1030 -#define IDC_CHECK_MIRC_ACTION 1031 -#define IDC_CHECK_MIRC_MULTISERVER 1032 -#define IDC_CHECK_NOTIFY_NOTRECOGNIZED 1033 -#define IDC_CHECK_NOTIFY_RECOGNIZED 1034 -#define IDC_CHECK_SKYPE 1035 -#define IDC_CHECK_START_CHECKEPS 1036 -#define IDC_CHECK_START_LOGIN 1037 -#define IDC_CHECK_START_MINIMIZE 1038 -#define IDC_CHECK_START_VERSION 1039 -#define IDC_CHECK_TORRENT_AUTOCHECK 1040 -#define IDC_CHECK_TORRENT_AUTOCREATEFOLDER 1041 -#define IDC_CHECK_TORRENT_AUTOSETFOLDER 1042 -#define IDC_CHECK_TORRENT_AUTOUSEFOLDER 1043 -#define IDC_CHECK_TORRENT_FILTER 1044 -#define IDC_CHECK_TWITTER 1045 -#define IDC_CHECK_UPDATE_CHECKMP 1046 -#define IDC_CHECK_UPDATE_CONFIRM 1047 -#define IDC_CHECK_UPDATE_GOTO 1048 -#define IDC_CHECK_UPDATE_RANGE 1049 -#define IDC_CHECK_UPDATE_ROOT 1050 -#define IDC_CHECK_UPDATE_WAITMP 1051 -#define IDC_COMBO_ANIME_SCORE 1052 -#define IDC_COMBO_ANIME_STATUS 1053 -#define IDC_COMBO_DBLCLICK 1054 -#define IDC_COMBO_FEED_ELEMENT 1055 -#define IDC_COMBO_FEED_FILTER_ACTION 1056 -#define IDC_COMBO_FEED_FILTER_MATCH 1057 -#define IDC_COMBO_FEED_FILTER_OPTION 1058 -#define IDC_COMBO_FEED_OPERATOR 1059 -#define IDC_COMBO_FEED_VALUE 1060 -#define IDC_COMBO_MDLCLICK 1061 -#define IDC_COMBO_THEME 1062 -#define IDC_COMBO_TORRENT_FOLDER 1063 -#define IDC_COMBO_TORRENT_SEARCH 1064 -#define IDC_COMBO_TORRENT_SOURCE 1065 -#define IDC_DATETIME_FINISH 1066 -#define IDC_DATETIME_START 1067 -#define IDC_EDIT_ANIME_ALT 1068 -#define IDC_EDIT_ANIME_FOLDER 1069 -#define IDC_EDIT_ANIME_PROGRESS 1070 -#define IDC_EDIT_ANIME_SYNOPSIS 1071 -#define IDC_EDIT_ANIME_TAGS 1072 -#define IDC_EDIT_ANIME_TITLE 1073 -#define IDC_EDIT_DELAY 1074 -#define IDC_EDIT_EXTERNALLINKS 1075 -#define IDC_EDIT_FEED_NAME 1076 -#define IDC_EDIT_HTTP_URL 1077 -#define IDC_EDIT_INPUT 1078 -#define IDC_EDIT_MIRC_CHANNELS 1079 -#define IDC_EDIT_MIRC_SERVICE 1080 -#define IDC_EDIT_PASS 1081 -#define IDC_EDIT_PREVIEW 1082 -#define IDC_EDIT_PROXY_HOST 1083 -#define IDC_EDIT_PROXY_PASS 1084 -#define IDC_EDIT_PROXY_USER 1085 -#define IDC_EDIT_SEARCH 1086 -#define IDC_EDIT_TORRENT_APP 1087 -#define IDC_EDIT_TORRENT_INTERVAL 1088 -#define IDC_EDIT_USER 1089 -#define IDC_LINK_ANIME_FANSUB 1090 -#define IDC_LINK_DEFAULTS 1091 -#define IDC_LINK_MAL 1092 -#define IDC_LINK_NOWPLAYING 1093 -#define IDC_LINK_THEMES 1094 -#define IDC_LINK_TWITTER 1095 -#define IDC_LIST_EVENT 1096 -#define IDC_LIST_FEED_FILTER_ANIME 1097 -#define IDC_LIST_FEED_FILTER_CONDITIONS 1098 -#define IDC_LIST_FEED_FILTER_PRESETS 1099 -#define IDC_LIST_FOLDERS_ROOT 1100 -#define IDC_LIST_MAIN 1101 -#define IDC_LIST_MEDIA 1102 -#define IDC_LIST_SEARCH 1103 -#define IDC_LIST_SEASON 1104 -#define IDC_LIST_STREAM_PROVIDER 1105 -#define IDC_LIST_TEST_RECOGNITION 1106 -#define IDC_LIST_TORRENT 1107 -#define IDC_LIST_TORRENT_FILTER 1108 -#define IDC_PROGRESS_UPDATE 1109 -#define IDC_RADIO_MIRC_CHANNEL1 1110 -#define IDC_RADIO_MIRC_CHANNEL2 1111 -#define IDC_RADIO_MIRC_CHANNEL3 1112 -#define IDC_RADIO_TORRENT_APP1 1113 -#define IDC_RADIO_TORRENT_APP2 1114 -#define IDC_RADIO_TORRENT_NEW1 1115 -#define IDC_RADIO_TORRENT_NEW2 1116 -#define IDC_REBAR_MAIN 1117 -#define IDC_REBAR_SEASON 1118 -#define IDC_REBAR_TORRENT 1119 -#define IDC_RICHEDIT_ABOUT 1120 -#define IDC_RICHEDIT_FORMAT 1121 -#define IDC_RICHEDIT_UPDATE 1122 -#define IDC_SPIN_DELAY 1123 -#define IDC_SPIN_INPUT 1124 -#define IDC_SPIN_PROGRESS 1125 -#define IDC_SPIN_TORRENT_INTERVAL 1126 -#define IDC_STATIC_ANIME_DETAILS 1127 -#define IDC_STATIC_ANIME_IMG 1128 -#define IDC_STATIC_ANIME_STAT1 1129 -#define IDC_STATIC_ANIME_STAT2 1130 -#define IDC_STATIC_ANIME_STAT3 1131 -#define IDC_STATIC_ANIME_STAT4 1132 -#define IDC_STATIC_CACHE1 1133 -#define IDC_STATIC_CACHE2 1134 -#define IDC_STATIC_CACHE3 1135 -#define IDC_STATIC_FEED_FILTER_DISCARDTYPE 1136 -#define IDC_STATIC_FEED_FILTER_LIMIT 1137 -#define IDC_STATIC_HEADER 1138 -#define IDC_STATIC_HEADER1 1139 -#define IDC_STATIC_HEADER2 1140 -#define IDC_STATIC_HEADER3 1141 -#define IDC_STATIC_HEADER4 1142 -#define IDC_STATIC_INPUTINFO 1143 -#define IDC_STATIC_TITLE 1144 -#define IDC_STATIC_UPDATE_DETAILS 1145 -#define IDC_STATIC_UPDATE_PROGRESS 1146 -#define IDC_STATIC_UPDATE_TITLE 1147 -#define IDC_STATUSBAR_MAIN 1148 -#define IDC_TAB_ANIME 1149 -#define IDC_TAB_MAIN 1150 -#define IDC_TAB_PAGES 1151 -#define IDC_TOOLBAR_FEED_FILTER 1152 -#define IDC_TOOLBAR_MAIN 1153 -#define IDC_TOOLBAR_MENU 1154 -#define IDC_TOOLBAR_SEARCH 1155 -#define IDC_TOOLBAR_SEASON 1156 -#define IDC_TOOLBAR_TORRENT 1157 -#define IDC_TREE_MAIN 1158 -#define IDC_TREE_SECTIONS 1159 -#define IDS_KEYWORD_AUDIO 40000 -#define IDS_KEYWORD_EXTENSION 40001 -#define IDS_KEYWORD_EXTRA 40002 -#define IDS_KEYWORD_EXTRA_UNSAFE 40003 -#define IDS_KEYWORD_VERSION 40004 -#define IDS_KEYWORD_VIDEO 40005 -#define IDS_KEYWORD_EPISODE 40006 -#define IDS_KEYWORD_EPISODE_PREFIX 40007 diff --git a/search.cpp b/search.cpp deleted file mode 100644 index 4d6326c6d..000000000 --- a/search.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "anime.h" -#include "anime_db.h" -#include "anime_episode.h" -#include "anime_item.h" -#include "common.h" -#include "foreach.h" -#include "logger.h" -#include "myanimelist.h" -#include "recognition.h" -#include "settings.h" -#include "string.h" -#include "taiga.h" - -#include "dlg/dlg_main.h" -#include "dlg/dlg_settings.h" - -#include "win32/win_taskbar.h" -#include "win32/win_taskdialog.h" - -// ============================================================================= - -// Extends the length limit from 260 to 32767 characters -wstring GetExtendedLengthPath(const wstring& path) { - if (!StartsWith(path, L"\\\\?\\")) - return L"\\\\?\\" + path; - return path; -} - -bool IsDirectory(const WIN32_FIND_DATA& win32_find_data) { - return (win32_find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; -} - -bool IsValidDirectory(const WIN32_FIND_DATA& win32_find_data) { - return wcscmp(win32_find_data.cFileName, L".") != 0 && - wcscmp(win32_find_data.cFileName, L"..") != 0; -} - -wstring SearchFileFolder(anime::Item& anime_item, const wstring& root, - int episode_number, bool search_folder) { - if (root.empty()) - return wstring(); - - anime::Episode episode; - wstring path = AddTrailingSlash(GetExtendedLengthPath(root)) + L"*"; - - WIN32_FIND_DATA win32_find_data; - HANDLE handle = FindFirstFile(path.c_str(), &win32_find_data); - - do { - if (handle == INVALID_HANDLE_VALUE) { - DWORD error_code = GetLastError(); - switch (error_code) { - case ERROR_SUCCESS: - LOG(LevelError, L"Error code is unavailable."); - break; - case ERROR_FILE_NOT_FOUND: - LOG(LevelError, L"No matching files were found."); - break; - default: - LOG(LevelError, FormatError(error_code)); - break; - } - LOG(LevelError, L"Path: " + path); - SetLastError(ERROR_SUCCESS); - return wstring(); - } - - // Folders - if (IsDirectory(win32_find_data)) { - if (IsValidDirectory(win32_find_data)) { - // Check root folder - if (search_folder == true) { - if (Meow.ExamineTitle(win32_find_data.cFileName, episode, - false, false, false, false, false)) { - if (Meow.CompareEpisode(episode, anime_item, true, false, false)) { - FindClose(handle); - return AddTrailingSlash(root) + win32_find_data.cFileName; - } - } - } - // Check sub folders - path = AddTrailingSlash(root) + win32_find_data.cFileName; - path = SearchFileFolder(anime_item, path, episode_number, search_folder); - if (!path.empty()) { - FindClose(handle); - return path; - } - } - - // Files - } else { - if (search_folder == false) { - // Check file size -- anything less than 10 MB can't be a new episode - if (win32_find_data.nFileSizeLow > 1024 * 1024 * 10) { - // Examine file name and extract episode data - if (Meow.ExamineTitle(win32_find_data.cFileName, episode, - true, true, true, true, true)) { - // Compare episode data with anime title - if (Meow.CompareEpisode(episode, anime_item)) { - int number = GetEpisodeHigh(episode.number); - int numberlow = GetEpisodeLow(episode.number); - for (int i = numberlow; i <= number; i++) { - anime_item.SetEpisodeAvailability(i, true, root + win32_find_data.cFileName); - } - if (episode_number == 0 || (episode_number >= numberlow && episode_number <= number)) { - FindClose(handle); - return AddTrailingSlash(root) + win32_find_data.cFileName; - } - } - } - } else { - LOG(LevelDebug, L"File is ignored because its size does not meet the threshold."); - LOG(LevelDebug, L"Path: " + AddTrailingSlash(root) + win32_find_data.cFileName); - } - } - } - } while (FindNextFile(handle, &win32_find_data)); - - FindClose(handle); - return wstring(); -} - -// ============================================================================= - -void ScanAvailableEpisodes(int anime_id, bool check_folder, bool silent) { - // Check if any root folder is available - if (!silent && Settings.Folders.root.empty()) { - win32::TaskDialog dlg(APP_TITLE, TD_ICON_INFORMATION); - dlg.SetMainInstruction(L"Would you like to set root anime folders first?"); - dlg.SetContent(L"You need to have at least one root folder set before scanning available episodes."); - dlg.AddButton(L"Yes", IDYES); - dlg.AddButton(L"No", IDNO); - dlg.Show(g_hMain); - if (dlg.GetSelectedButtonID() == IDYES) - ExecuteAction(L"Settings", SECTION_LIBRARY, PAGE_LIBRARY_FOLDERS); - return; - } - - int episode_number = Settings.Program.List.progress_show_available ? -1 : 0; - - // Search for all list items - if (!anime_id) { - size_t i = 0; - // Search is made in reverse to give new items priority. The user is - // probably more interested in them than the older titles. - if (!silent) { - TaskbarList.SetProgressState(TBPF_NORMAL); - SetSharedCursor(IDC_WAIT); - } - foreach_r_(it, AnimeDatabase.items) { - if (!silent) - TaskbarList.SetProgressValue(i++, AnimeDatabase.items.size()); - switch (it->second.GetMyStatus()) { - case mal::MYSTATUS_WATCHING: - if (!silent) - MainDialog.ChangeStatus(L"Scanning... (" + it->second.GetTitle() + L")"); - it->second.CheckEpisodes(episode_number, check_folder); - } - } - i = 0; - foreach_r_(it, AnimeDatabase.items) { - if (!silent) - TaskbarList.SetProgressValue(i++, AnimeDatabase.items.size()); - switch (it->second.GetMyStatus()) { - case mal::MYSTATUS_ONHOLD: - case mal::MYSTATUS_PLANTOWATCH: - if (!silent) - MainDialog.ChangeStatus(L"Scanning... (" + it->second.GetTitle() + L")"); - it->second.CheckEpisodes(episode_number, check_folder); - } - } - if (!silent) { - TaskbarList.SetProgressState(TBPF_NOPROGRESS); - SetSharedCursor(IDC_ARROW); - } - - // Search for a single item - } else { - SetSharedCursor(IDC_WAIT); - auto anime_item = AnimeDatabase.FindItem(anime_id); - if (anime_item) - anime_item->CheckEpisodes(episode_number, true); - SetSharedCursor(IDC_ARROW); - } - - // We're done - if (!silent) - MainDialog.ChangeStatus(L"Scan finished."); -} \ No newline at end of file diff --git a/settings.cpp b/settings.cpp deleted file mode 100644 index 1eda7cf3a..000000000 --- a/settings.cpp +++ /dev/null @@ -1,744 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "settings.h" - -#include "dlg/dlg_anime_info.h" -#include "dlg/dlg_anime_list.h" -#include "dlg/dlg_main.h" -#include "dlg/dlg_history.h" -#include "dlg/dlg_search.h" -#include "dlg/dlg_settings.h" -#include "dlg/dlg_stats.h" - -#include "anime.h" -#include "anime_db.h" -#include "anime_filter.h" -#include "common.h" -#include "foreach.h" -#include "gfx.h" -#include "history.h" -#include "http.h" -#include "logger.h" -#include "media.h" -#include "monitor.h" -#include "stats.h" -#include "string.h" -#include "taiga.h" -#include "theme.h" -#include "xml.h" - -#include "win32/win_registry.h" -#include "win32/win_taskdialog.h" - -#define DEFAULT_EXTERNALLINKS L"Anime Recommendation Finder|http://www.animerecs.com\r\nMALgraph|http://mal.oko.im\r\n-\r\nAnime Season Discussion Group|http://myanimelist.net/clubs.php?cid=743\r\nMahou Showtime Schedule|http://www.mahou.org/Showtime/?o=ET#Current\r\nThe Fansub Wiki|http://www.fansubwiki.com" -#define DEFAULT_FORMAT_HTTP L"user=%user%&name=%title%&ep=%episode%&eptotal=$if(%total%,%total%,?)&score=%score%&picurl=%image%&playstatus=%playstatus%" -#define DEFAULT_FORMAT_MESSENGER L"Watching: %title%$if(%episode%, #%episode%$if(%total%,/%total%)) ~ www.myanimelist.net/anime/%id%" -#define DEFAULT_FORMAT_MIRC L"\00304$if($greater(%episode%,%watched%),Watching,Re-watching):\003 %title%$if(%episode%, \00303%episode%$if(%total%,/%total%))\003 $if(%score%,\00314[Score: %score%/10]\003) \00312www.myanimelist.net/anime/%id%" -#define DEFAULT_FORMAT_SKYPE L"Watching: %title%$if(%episode%, #%episode%$if(%total%,/%total%))" -#define DEFAULT_FORMAT_TWITTER L"$ifequal(%episode%,%total%,Just completed: %title%$if(%score%, (Score: %score%/10)) www.myanimelist.net/anime/%id%)" -#define DEFAULT_FORMAT_BALLOON L"$if(%title%,%title%)\\n$if(%episode%,Episode %episode%$if(%total%,/%total%) )$if(%group%,by %group%)\\n$if(%name%,%name%)" -#define DEFAULT_TORRENT_APPPATH L"C:\\Program Files\\uTorrent\\uTorrent.exe" -#define DEFAULT_TORRENT_SEARCH L"http://www.nyaa.se/?page=rss&cats=1_37&filter=2&term=%title%" -#define DEFAULT_TORRENT_SOURCE L"http://tokyotosho.info/rss.php?filter=1,11&zwnj=0" - -class Settings Settings; - -// ============================================================================= - -bool Settings::Load() { - // Initialize - folder_ = Taiga.GetDataPath(); - file_ = folder_ + L"settings.xml"; - CreateDirectory(folder_.c_str(), NULL); - - // Load XML file - xml_document doc; - xml_parse_result result = doc.load_file(file_.c_str()); - - // Read settings - xml_node settings = doc.child(L"settings"); - - // Meta - xml_node meta = settings.child(L"meta"); - // Version - xml_node version = meta.child(L"version"); - Meta.Version.major = version.attribute(L"major").as_int(); - Meta.Version.minor = version.attribute(L"minor").as_int(); - Meta.Version.revision = version.attribute(L"revision").as_int(); - - // Account - xml_node account = settings.child(L"account"); - // MyAnimeList - xml_node mal = account.child(L"myanimelist"); - Account.MAL.auto_sync = mal.attribute(L"login").as_int(); - Account.MAL.password = SimpleDecrypt(mal.attribute(L"password").value()); - Account.MAL.user = mal.attribute(L"username").value(); - // Update - xml_node update = account.child(L"update"); - Account.Update.ask_to_confirm = update.attribute(L"asktoconfirm").as_int(TRUE); - Account.Update.check_player = update.attribute(L"checkplayer").as_int(); - Account.Update.delay = update.attribute(L"delay").as_int(120); - Account.Update.go_to_nowplaying = update.attribute(L"gotonowplaying").as_int(TRUE); - Account.Update.out_of_range = update.attribute(L"outofrange").as_int(); - Account.Update.out_of_root = update.attribute(L"outofroot").as_int(); - Account.Update.wait_mp = update.attribute(L"waitplayer").as_int(); - - // Announcements - xml_node announce = settings.child(L"announce"); - // HTTP - xml_node http = announce.child(L"http"); - Announce.HTTP.enabled = http.attribute(L"enabled").as_int(); - Announce.HTTP.format = http.attribute(L"format").as_string(DEFAULT_FORMAT_HTTP); - Announce.HTTP.url = http.attribute(L"url").value(); - // MSN - xml_node messenger = announce.child(L"messenger"); - Announce.MSN.enabled = messenger.attribute(L"enabled").as_int(); - Announce.MSN.format = messenger.attribute(L"format").as_string(DEFAULT_FORMAT_MESSENGER); - // mIRC - xml_node mirc = announce.child(L"mirc"); - Announce.MIRC.channels = mirc.attribute(L"channels").as_string(L"#myanimelist, #taiga"); - Announce.MIRC.enabled = mirc.attribute(L"enabled").as_int(); - Announce.MIRC.format = mirc.attribute(L"format").as_string(DEFAULT_FORMAT_MIRC); - Announce.MIRC.mode = mirc.attribute(L"mode").as_int(1); - Announce.MIRC.multi_server = mirc.attribute(L"multiserver").as_int(FALSE); - Announce.MIRC.service = mirc.attribute(L"service").as_string(L"mIRC"); - Announce.MIRC.use_action = mirc.attribute(L"useaction").as_int(TRUE); - // Skype - xml_node skype = announce.child(L"skype"); - Announce.Skype.enabled = skype.attribute(L"enabled").as_int(); - Announce.Skype.format = skype.attribute(L"format").as_string(DEFAULT_FORMAT_SKYPE); - // Twitter - xml_node twitter = announce.child(L"twitter"); - Announce.Twitter.enabled = twitter.attribute(L"enabled").as_int(); - Announce.Twitter.format = twitter.attribute(L"format").as_string(DEFAULT_FORMAT_TWITTER); - Announce.Twitter.oauth_key = twitter.attribute(L"oauth_token").value(); - Announce.Twitter.oauth_secret = twitter.attribute(L"oauth_secret").value(); - Announce.Twitter.user = twitter.attribute(L"user").value(); - - // Folders - Folders.root.clear(); - xml_node folders = settings.child(L"anime").child(L"folders"); - for (xml_node folder = folders.child(L"root"); folder; folder = folder.next_sibling(L"root")) { - Folders.root.push_back(folder.attribute(L"folder").value()); - } - xml_node watch = folders.child(L"watch"); - Folders.watch_enabled = watch.attribute(L"enabled").as_int(TRUE); - - // Anime items - xml_node items = settings.child(L"anime").child(L"items"); - for (xml_node item = items.child(L"item"); item; item = item.next_sibling(L"item")) { - int anime_id = item.attribute(L"id").as_int(); - auto anime_item = AnimeDatabase.FindItem(anime_id); - if (!anime_item) { - anime_item = &AnimeDatabase.items[anime_id]; - } - anime_item->SetFolder(item.attribute(L"folder").value()); - anime_item->SetUserSynonyms(item.attribute(L"titles").value()); - anime_item->SetUseAlternative(item.attribute(L"use_alternative").as_bool()); - } - - // Program - xml_node program = settings.child(L"program"); - // General - xml_node general = program.child(L"general"); - Program.General.auto_start = general.attribute(L"autostart").as_int(); - Program.General.close = general.attribute(L"close").as_int(); - Program.General.minimize = general.attribute(L"minimize").as_int(); - Program.General.theme = general.attribute(L"theme").as_string(L"Default"); - Program.General.external_links = general.attribute(L"externallinks").as_string(DEFAULT_EXTERNALLINKS); - Program.General.hide_sidebar = general.attribute(L"hidesidebar").as_int(); - Program.General.enable_recognition = general.attribute(L"enablerecognition").as_int(TRUE); - Program.General.enable_sharing = general.attribute(L"enablesharing").as_int(TRUE); - Program.General.enable_sync = general.attribute(L"enablesync").as_int(TRUE); - // Position - xml_node position = program.child(L"position"); - Program.Position.x = position.attribute(L"x").as_int(-1); - Program.Position.y = position.attribute(L"y").as_int(-1); - Program.Position.w = position.attribute(L"w").as_int(960); - Program.Position.h = position.attribute(L"h").as_int(640); - Program.Position.maximized = position.attribute(L"maximized").as_int(); - // Start-up - xml_node startup = program.child(L"startup"); - Program.StartUp.check_new_episodes = startup.attribute(L"checkeps").as_int(); - Program.StartUp.check_new_version = startup.attribute(L"checkversion").as_int(TRUE); - Program.StartUp.minimize = startup.attribute(L"minimize").as_int(); - // Exit - xml_node exit = program.child(L"exit"); - Program.Exit.remember_pos_size = exit.attribute(L"remember_pos_size").as_int(TRUE); - // Proxy - xml_node proxy = program.child(L"proxy"); - Program.Proxy.host = proxy.attribute(L"host").value(); - Program.Proxy.password = SimpleDecrypt(proxy.attribute(L"password").value()); - Program.Proxy.user = proxy.attribute(L"username").value(); - SetProxies(Program.Proxy.host, Program.Proxy.user, Program.Proxy.password); - // List - xml_node list = program.child(L"list"); - Program.List.double_click = list.child(L"action").attribute(L"doubleclick").as_int(4); - Program.List.middle_click = list.child(L"action").attribute(L"middleclick").as_int(3); - Program.List.english_titles = list.child(L"action").attribute(L"englishtitles").as_bool(); - Program.List.highlight = list.child(L"filter").child(L"episodes").attribute(L"highlight").as_int(TRUE); - Program.List.progress_show_aired = list.child(L"progress").attribute(L"showaired").as_int(TRUE); - Program.List.progress_show_available = list.child(L"progress").attribute(L"showavailable").as_int(TRUE); - Program.List.sort_column = list.child(L"sort").attribute(L"column").as_int(0); - Program.List.sort_order = list.child(L"sort").attribute(L"order").as_int(1); - // Notifications - xml_node notifications = program.child(L"notifications"); - Program.Notifications.recognized = notifications.child(L"balloon").attribute(L"recognized").as_int(TRUE); - Program.Notifications.notrecognized = notifications.child(L"balloon").attribute(L"notrecognized").as_int(TRUE); - Program.Notifications.format = notifications.child(L"balloon").attribute(L"format").as_string(DEFAULT_FORMAT_BALLOON); - - // Recognition - xml_node recognition = settings.child(L"recognition"); - // Media players - xml_node mediaplayers = recognition.child(L"mediaplayers"); - for (xml_node player = mediaplayers.child(L"player"); player; player = player.next_sibling(L"player")) { - wstring name = player.attribute(L"name").value(); - bool enabled = player.attribute(L"enabled").as_bool(); - foreach_(it, MediaPlayers.items) { - if (it->name == name) { - it->enabled = enabled; - break; - } - } - } - // Streaming - xml_node streaming = recognition.child(L"streaming"); - Recognition.Streaming.ann_enabled = streaming.child(L"providers").attribute(L"ann").as_bool(); - Recognition.Streaming.crunchyroll_enabled = streaming.child(L"providers").attribute(L"crunchyroll").as_bool(); - Recognition.Streaming.veoh_enabled = streaming.child(L"providers").attribute(L"veoh").as_bool(); - Recognition.Streaming.viz_enabled = streaming.child(L"providers").attribute(L"viz").as_bool(); - Recognition.Streaming.youtube_enabled = streaming.child(L"providers").attribute(L"youtube").as_bool(); - - // RSS - xml_node rss = settings.child(L"rss"); - // Torrent - xml_node torrent = rss.child(L"torrent"); - // General - RSS.Torrent.app_mode = torrent.child(L"application").attribute(L"mode").as_int(1); - RSS.Torrent.app_path = torrent.child(L"application").attribute(L"path").value(); - if (RSS.Torrent.app_path.empty()) { - RSS.Torrent.app_path = GetDefaultAppPath(L".torrent", DEFAULT_TORRENT_APPPATH); - } - RSS.Torrent.check_enabled = torrent.child(L"options").attribute(L"autocheck").as_int(TRUE); - RSS.Torrent.check_interval = torrent.child(L"options").attribute(L"checkinterval").as_int(60); - RSS.Torrent.create_folder = torrent.child(L"options").attribute(L"autocreatefolder").as_int(FALSE); - RSS.Torrent.download_path = torrent.child(L"options").attribute(L"downloadpath").as_string(); - RSS.Torrent.new_action = torrent.child(L"options").attribute(L"newaction").as_int(1); - RSS.Torrent.set_folder = torrent.child(L"options").attribute(L"autosetfolder").as_int(TRUE); - RSS.Torrent.search_url = torrent.child(L"search").attribute(L"address").as_string(DEFAULT_TORRENT_SEARCH); - RSS.Torrent.source = torrent.child(L"source").attribute(L"address").as_string(DEFAULT_TORRENT_SOURCE); - RSS.Torrent.use_folder = torrent.child(L"options").attribute(L"autousefolder").as_int(FALSE); - // Filters - xml_node filter = torrent.child(L"filter"); - RSS.Torrent.Filters.global_enabled = filter.attribute(L"enabled").as_int(TRUE); - RSS.Torrent.Filters.archive_maxcount = filter.attribute(L"archive_maxcount").as_int(1000); - Aggregator.filter_manager.filters.clear(); - for (xml_node item = filter.child(L"item"); item; item = item.next_sibling(L"item")) { - Aggregator.filter_manager.AddFilter( - Aggregator.filter_manager.GetIndexFromShortcode(FEED_FILTER_SHORTCODE_ACTION, item.attribute(L"action").value()), - Aggregator.filter_manager.GetIndexFromShortcode(FEED_FILTER_SHORTCODE_MATCH, item.attribute(L"match").value()), - Aggregator.filter_manager.GetIndexFromShortcode(FEED_FILTER_SHORTCODE_OPTION, item.attribute(L"option").value()), - item.attribute(L"enabled").as_bool(), - item.attribute(L"name").value()); - for (xml_node anime = item.child(L"anime"); anime; anime = anime.next_sibling(L"anime")) { - Aggregator.filter_manager.filters.back().anime_ids.push_back(anime.attribute(L"id").as_int()); - } - for (xml_node condition = item.child(L"condition"); condition; condition = condition.next_sibling(L"condition")) { - Aggregator.filter_manager.filters.back().AddCondition( - Aggregator.filter_manager.GetIndexFromShortcode(FEED_FILTER_SHORTCODE_ELEMENT, condition.attribute(L"element").value()), - Aggregator.filter_manager.GetIndexFromShortcode(FEED_FILTER_SHORTCODE_OPERATOR, condition.attribute(L"operator").value()), - condition.attribute(L"value").value()); - } - } - if (Aggregator.filter_manager.filters.empty()) { - Aggregator.filter_manager.AddPresets(); - } - // Torrent source - Feed* feed = Aggregator.Get(FEED_CATEGORY_LINK); - if (feed) feed->link = RSS.Torrent.source; - // File archive - Aggregator.LoadArchive(); - - return result.status == status_ok; -} - -// ============================================================================= - -bool Settings::Save() { - // Initialize - xml_document doc; - xml_node settings = doc.append_child(L"settings"); - - // Meta - settings.append_child(node_comment).set_value(L" Meta "); - xml_node meta = settings.append_child(L"meta"); - // Version - xml_node version = meta.append_child(L"version"); - version.append_attribute(L"major") = VERSION_MAJOR; - version.append_attribute(L"minor") = VERSION_MINOR; - version.append_attribute(L"revision") = VERSION_REVISION; - - // Account - settings.append_child(node_comment).set_value(L" Account "); - xml_node account = settings.append_child(L"account"); - // MyAnimeList - xml_node mal = account.append_child(L"myanimelist"); - mal.append_attribute(L"username") = Account.MAL.user.c_str(); - mal.append_attribute(L"password") = SimpleEncrypt(Account.MAL.password).c_str(); - mal.append_attribute(L"login") = Account.MAL.auto_sync; - // Update - xml_node update = account.append_child(L"update"); - update.append_attribute(L"asktoconfirm") = Account.Update.ask_to_confirm; - update.append_attribute(L"delay") = Account.Update.delay; - update.append_attribute(L"checkplayer") = Account.Update.check_player; - update.append_attribute(L"gotonowplaying") = Account.Update.go_to_nowplaying; - update.append_attribute(L"outofrange") = Account.Update.out_of_range; - update.append_attribute(L"outofroot") = Account.Update.out_of_root; - update.append_attribute(L"waitplayer") = Account.Update.wait_mp; - - // Anime - settings.append_child(node_comment).set_value(L" Anime list "); - xml_node anime = settings.append_child(L"anime"); - // Root folders - xml_node folders = anime.append_child(L"folders"); - for (size_t i = 0; i < Folders.root.size(); i++) { - xml_node root = folders.append_child(L"root"); - root.append_attribute(L"folder") = Folders.root[i].c_str(); - } - xml_node watch = folders.append_child(L"watch"); - watch.append_attribute(L"enabled") = Folders.watch_enabled; - // Items - xml_node items = anime.append_child(L"items"); - foreach_(it, AnimeDatabase.items) { - anime::Item& anime_item = it->second; - if (anime_item.GetFolder().empty() && - !anime_item.UserSynonymsAvailable() && - !anime_item.GetUseAlternative()) - continue; - xml_node item = items.append_child(L"item"); - item.append_attribute(L"id") = anime_item.GetId(); - if (!anime_item.GetFolder().empty()) - item.append_attribute(L"folder") = anime_item.GetFolder().c_str(); - if (anime_item.UserSynonymsAvailable()) - item.append_attribute(L"titles") = Join(anime_item.GetUserSynonyms(), L"; ").c_str(); - if (anime_item.GetUseAlternative()) - item.append_attribute(L"use_alternative") = anime_item.GetUseAlternative(); - } - - // Announcements - settings.append_child(node_comment).set_value(L" Announcements "); - xml_node announce = settings.append_child(L"announce"); - // HTTP - xml_node http = announce.append_child(L"http"); - http.append_attribute(L"enabled") = Announce.HTTP.enabled; - http.append_attribute(L"format") = Announce.HTTP.format.c_str(); - http.append_attribute(L"url") = Announce.HTTP.url.c_str(); - // Messenger - settings.child(L"announce").append_child(L"messenger"); - settings.child(L"announce").child(L"messenger").append_attribute(L"enabled") = Announce.MSN.enabled; - settings.child(L"announce").child(L"messenger").append_attribute(L"format") = Announce.MSN.format.c_str(); - // mIRC - xml_node mirc = announce.append_child(L"mirc"); - mirc.append_attribute(L"enabled") = Announce.MIRC.enabled; - mirc.append_attribute(L"format") = Announce.MIRC.format.c_str(); - mirc.append_attribute(L"service") = Announce.MIRC.service.c_str(); - mirc.append_attribute(L"mode") = Announce.MIRC.mode; - mirc.append_attribute(L"useaction") = Announce.MIRC.use_action; - mirc.append_attribute(L"multiserver") = Announce.MIRC.multi_server; - mirc.append_attribute(L"channels") = Announce.MIRC.channels.c_str(); - // Skype - xml_node skype = announce.append_child(L"skype"); - skype.append_attribute(L"enabled") = Announce.Skype.enabled; - skype.append_attribute(L"format") = Announce.Skype.format.c_str(); - // Twitter - xml_node twitter = announce.append_child(L"twitter"); - twitter.append_attribute(L"enabled") = Announce.Twitter.enabled; - twitter.append_attribute(L"format") = Announce.Twitter.format.c_str(); - twitter.append_attribute(L"oauth_token") = Announce.Twitter.oauth_key.c_str(); - twitter.append_attribute(L"oauth_secret") = Announce.Twitter.oauth_secret.c_str(); - twitter.append_attribute(L"user") = Announce.Twitter.user.c_str(); - - // Program - settings.append_child(node_comment).set_value(L" Program "); - xml_node program = settings.append_child(L"program"); - // General - xml_node general = program.append_child(L"general"); - general.append_attribute(L"autostart") = Program.General.auto_start; - general.append_attribute(L"close") = Program.General.close; - general.append_attribute(L"minimize") = Program.General.minimize; - general.append_attribute(L"theme") = Program.General.theme.c_str(); - general.append_attribute(L"externallinks") = Program.General.external_links.c_str(); - general.append_attribute(L"hidesidebar") = Program.General.hide_sidebar; - general.append_attribute(L"enablerecognition") = Program.General.enable_recognition; - general.append_attribute(L"enablesharing") = Program.General.enable_sharing; - general.append_attribute(L"enablesync") = Program.General.enable_sync; - // Position - xml_node position = program.append_child(L"position"); - position.append_attribute(L"x") = Program.Position.x; - position.append_attribute(L"y") = Program.Position.y; - position.append_attribute(L"w") = Program.Position.w; - position.append_attribute(L"h") = Program.Position.h; - position.append_attribute(L"maximized") = Program.Position.maximized; - // Startup - xml_node startup = program.append_child(L"startup"); - startup.append_attribute(L"checkversion") = Program.StartUp.check_new_version; - startup.append_attribute(L"checkeps") = Program.StartUp.check_new_episodes; - startup.append_attribute(L"minimize") = Program.StartUp.minimize; - // Exit - xml_node exit = program.append_child(L"exit"); - exit.append_attribute(L"remember_pos_size") = Program.Exit.remember_pos_size; - // Proxy - xml_node proxy = program.append_child(L"proxy"); - proxy.append_attribute(L"host") = Program.Proxy.host.c_str(); - proxy.append_attribute(L"username") = Program.Proxy.user.c_str(); - proxy.append_attribute(L"password") = SimpleEncrypt(Program.Proxy.password).c_str(); - // List - xml_node list = program.append_child(L"list"); - // Actions - xml_node action = list.append_child(L"action"); - action.append_attribute(L"doubleclick") = Program.List.double_click; - action.append_attribute(L"middleclick") = Program.List.middle_click; - action.append_attribute(L"englishtitles") = Program.List.english_titles; - // Filter - xml_node filter = list.append_child(L"filter"); - filter.append_child(L"episodes"); - filter.child(L"episodes").append_attribute(L"highlight") = Program.List.highlight; - // Progress - xml_node progress = list.append_child(L"progress"); - progress.append_attribute(L"showaired") = Program.List.progress_show_aired; - progress.append_attribute(L"showavailable") = Program.List.progress_show_available; - // Sort - xml_node sort = list.append_child(L"sort"); - sort.append_attribute(L"column") = Program.List.sort_column; - sort.append_attribute(L"order") = Program.List.sort_order; - // Notifications - xml_node notifications = program.append_child(L"notifications"); - notifications.append_child(L"balloon"); - notifications.child(L"balloon").append_attribute(L"recognized") = Program.Notifications.recognized; - notifications.child(L"balloon").append_attribute(L"notrecognized") = Program.Notifications.notrecognized; - notifications.child(L"balloon").append_attribute(L"format") = Program.Notifications.format.c_str(); - - // Recognition - settings.append_child(node_comment).set_value(L" Recognition "); - xml_node recognition = settings.append_child(L"recognition"); - // Media players - xml_node mediaplayers = recognition.append_child(L"mediaplayers"); - foreach_(it, MediaPlayers.items) { - xml_node player = mediaplayers.append_child(L"player"); - player.append_attribute(L"name") = it->name.c_str(); - player.append_attribute(L"enabled") = it->enabled; - } - // Streaming - xml_node streaming = recognition.append_child(L"streaming"); - // Providers - xml_node providers = streaming.append_child(L"providers"); - providers.append_attribute(L"ann") = Recognition.Streaming.ann_enabled; - providers.append_attribute(L"crunchyroll") = Recognition.Streaming.crunchyroll_enabled; - providers.append_attribute(L"veoh") = Recognition.Streaming.veoh_enabled; - providers.append_attribute(L"viz") = Recognition.Streaming.viz_enabled; - providers.append_attribute(L"youtube") = Recognition.Streaming.youtube_enabled; - - // RSS - settings.append_child(node_comment).set_value(L" RSS "); - xml_node rss = settings.append_child(L"rss"); - // Torrent - xml_node torrent = rss.append_child(L"torrent"); - // General - torrent.append_child(L"application"); - torrent.child(L"application").append_attribute(L"mode") = RSS.Torrent.app_mode; - torrent.child(L"application").append_attribute(L"path") = RSS.Torrent.app_path.c_str(); - torrent.append_child(L"search"); - torrent.child(L"search").append_attribute(L"address") = RSS.Torrent.search_url.c_str(); - torrent.append_child(L"source"); - torrent.child(L"source").append_attribute(L"address") = RSS.Torrent.source.c_str(); - torrent.append_child(L"options"); - torrent.child(L"options").append_attribute(L"autocheck") = RSS.Torrent.check_enabled; - torrent.child(L"options").append_attribute(L"checkinterval") = RSS.Torrent.check_interval; - torrent.child(L"options").append_attribute(L"autosetfolder") = RSS.Torrent.set_folder; - torrent.child(L"options").append_attribute(L"autousefolder") = RSS.Torrent.use_folder; - torrent.child(L"options").append_attribute(L"autocreatefolder") = RSS.Torrent.create_folder; - torrent.child(L"options").append_attribute(L"downloadpath") = RSS.Torrent.download_path.c_str(); - torrent.child(L"options").append_attribute(L"newaction") = RSS.Torrent.new_action; - // Filter - xml_node torrent_filter = torrent.append_child(L"filter"); - torrent_filter.append_attribute(L"enabled") = RSS.Torrent.Filters.global_enabled; - torrent_filter.append_attribute(L"archive_maxcount") = RSS.Torrent.Filters.archive_maxcount; - for (auto it = Aggregator.filter_manager.filters.begin(); it != Aggregator.filter_manager.filters.end(); ++it) { - xml_node item = torrent_filter.append_child(L"item"); - item.append_attribute(L"action") = Aggregator.filter_manager.GetShortcodeFromIndex(FEED_FILTER_SHORTCODE_ACTION, it->action).c_str(); - item.append_attribute(L"match") = Aggregator.filter_manager.GetShortcodeFromIndex(FEED_FILTER_SHORTCODE_MATCH, it->match).c_str(); - item.append_attribute(L"option") = Aggregator.filter_manager.GetShortcodeFromIndex(FEED_FILTER_SHORTCODE_OPTION, it->option).c_str(); - item.append_attribute(L"enabled") = it->enabled; - item.append_attribute(L"name") = it->name.c_str(); - for (auto ita = it->anime_ids.begin(); ita != it->anime_ids.end(); ++ita) { - xml_node anime = item.append_child(L"anime"); - anime.append_attribute(L"id") = *ita; - } - for (auto itc = it->conditions.begin(); itc != it->conditions.end(); ++itc) { - xml_node condition = item.append_child(L"condition"); - condition.append_attribute(L"element") = Aggregator.filter_manager.GetShortcodeFromIndex(FEED_FILTER_SHORTCODE_ELEMENT, itc->element).c_str(); - condition.append_attribute(L"operator") = Aggregator.filter_manager.GetShortcodeFromIndex(FEED_FILTER_SHORTCODE_OPERATOR, itc->op).c_str(); - condition.append_attribute(L"value") = itc->value.c_str(); - } - } - - // Write registry - win32::Registry reg; - reg.OpenKey(HKEY_CURRENT_USER, - L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_SET_VALUE); - if (Program.General.auto_start) { - wstring app_path = Taiga.GetModulePath(); - reg.SetValue(APP_NAME, app_path.c_str()); - } else { - reg.DeleteValue(APP_NAME); - } - reg.CloseKey(); - - // Save file - ::CreateDirectory(folder_.c_str(), NULL); - return doc.save_file(file_.c_str(), L"\x09", format_default | format_write_bom); -} - -// ============================================================================= - -void Settings::ApplyChanges(const wstring& previous_user, const wstring& previous_theme) { - if (Program.General.theme != previous_theme) { - UI.Load(Program.General.theme); - UI.LoadImages(); - MainDialog.rebar.RedrawWindow(); - UpdateAllMenus(); - } - - if (Account.MAL.user != previous_user) { - AnimeDatabase.LoadList(); - History.Load(); - CurrentEpisode.Set(anime::ID_UNKNOWN); - MainDialog.treeview.RefreshHistoryCounter(); - MainDialog.UpdateTitle(); - AnimeListDialog.RefreshList(mal::MYSTATUS_WATCHING); - AnimeListDialog.RefreshTabs(mal::MYSTATUS_WATCHING); - HistoryDialog.RefreshList(); - NowPlayingDialog.Refresh(); - SearchDialog.RefreshList(); - Stats.CalculateAll(); - StatsDialog.Refresh(); - Taiga.logged_in = false; - } else { - AnimeListDialog.RefreshList(); - } - - FolderMonitor.Enable(Folders.watch_enabled == TRUE); - - SetProxies(Program.Proxy.host, - Program.Proxy.user, - Program.Proxy.password); - - UpdateExternalLinksMenu(); -} - -void Settings::HandleCompatibility() { - if (Meta.Version.revision == VERSION_REVISION) - return; - - // Convert old torrent filters to the new format - if (Meta.Version.revision < 246) { - LOG(LevelWarning, L"Converting torrent filters to the new format..."); - - xml_document doc; - xml_parse_result result = doc.load_file(file_.c_str()); - xml_node settings = doc.child(L"settings"); - xml_node rss = settings.child(L"rss"); - xml_node torrent = rss.child(L"torrent"); - xml_node filter = torrent.child(L"filter"); - - Aggregator.filter_manager.filters.clear(); - - for (xml_node item = filter.child(L"item"); item; item = item.next_sibling(L"item")) { - int action = item.attribute(L"action").as_int(); - int match = item.attribute(L"match").as_int(); - bool enabled = item.attribute(L"enabled").as_bool(); - wstring value = item.attribute(L"name").value(); - Aggregator.filter_manager.AddFilter(action, match, FEED_FILTER_OPTION_DEFAULT, enabled, value); - - for (xml_node anime = item.child(L"anime"); anime; anime = anime.next_sibling(L"anime")) { - Aggregator.filter_manager.filters.back().anime_ids.push_back(anime.attribute(L"id").as_int()); - } - - for (xml_node condition = item.child(L"condition"); condition; condition = condition.next_sibling(L"condition")) { - int element = condition.attribute(L"element").as_int(); - switch (element) { - case 0: // FEED_FILTER_ELEMENT_TITLE: - element = FEED_FILTER_ELEMENT_FILE_TITLE; break; - case 1: // FEED_FILTER_ELEMENT_CATEGORY: - element = FEED_FILTER_ELEMENT_FILE_CATEGORY; break; - case 2: // FEED_FILTER_ELEMENT_DESCRIPTION: - element = FEED_FILTER_ELEMENT_FILE_DESCRIPTION; break; - case 3: // FEED_FILTER_ELEMENT_LINK: - element = FEED_FILTER_ELEMENT_FILE_LINK; break; - case 4: // FEED_FILTER_ELEMENT_ANIME_ID: - element = FEED_FILTER_ELEMENT_META_ID; break; - case 5: // FEED_FILTER_ELEMENT_ANIME_TITLE: - element = FEED_FILTER_ELEMENT_EPISODE_TITLE; break; - case 6: // FEED_FILTER_ELEMENT_ANIME_SERIES_STATUS: - element = FEED_FILTER_ELEMENT_META_STATUS; break; - case 7: // FEED_FILTER_ELEMENT_ANIME_MY_STATUS: - element = FEED_FILTER_ELEMENT_USER_STATUS; break; - case 8: // FEED_FILTER_ELEMENT_ANIME_EPISODE_NUMBER: - element = FEED_FILTER_ELEMENT_EPISODE_NUMBER; break; - case 9: // FEED_FILTER_ELEMENT_ANIME_EPISODE_VERSION: - element = FEED_FILTER_ELEMENT_EPISODE_VERSION; break; - case 10: // FEED_FILTER_ELEMENT_ANIME_EPISODE_AVAILABLE: - element = FEED_FILTER_ELEMENT_LOCAL_EPISODE_AVAILABLE; break; - case 11: // FEED_FILTER_ELEMENT_ANIME_GROUP: - element = FEED_FILTER_ELEMENT_EPISODE_GROUP; break; - case 12: // FEED_FILTER_ELEMENT_ANIME_VIDEO_RESOLUTION: - element = FEED_FILTER_ELEMENT_EPISODE_VIDEO_RESOLUTION; break; - case 13: // FEED_FILTER_ELEMENT_ANIME_VIDEO_TYPE: - element = FEED_FILTER_ELEMENT_EPISODE_VIDEO_TYPE; break; - case 14: // FEED_FILTER_ELEMENT_ANIME_SERIES_DATE_START: - element = FEED_FILTER_ELEMENT_META_DATE_START; break; - case 15: // FEED_FILTER_ELEMENT_ANIME_SERIES_DATE_END: - element = FEED_FILTER_ELEMENT_META_DATE_END; break; - case 16: // FEED_FILTER_ELEMENT_ANIME_SERIES_EPISODES: - element = FEED_FILTER_ELEMENT_META_EPISODES; break; - case 17: // FEED_FILTER_ELEMENT_ANIME_SERIES_TYPE: - element = FEED_FILTER_ELEMENT_META_TYPE; break; - } - - int op = condition.attribute(L"op").as_int(); - switch (op) { - case 0: // FEED_FILTER_OPERATOR_IS - op = FEED_FILTER_OPERATOR_EQUALS; break; - case 1: // FEED_FILTER_OPERATOR_ISNOT - op = FEED_FILTER_OPERATOR_NOTEQUALS; break; - case 2: // FEED_FILTER_OPERATOR_ISGREATERTHAN - op = FEED_FILTER_OPERATOR_ISGREATERTHAN; break; - case 3: // FEED_FILTER_OPERATOR_ISLESSTHAN - op = FEED_FILTER_OPERATOR_ISLESSTHAN; break; - case 4: // FEED_FILTER_OPERATOR_BEGINSWITH - op = FEED_FILTER_OPERATOR_BEGINSWITH; break; - case 5: // FEED_FILTER_OPERATOR_ENDSWITH - op = FEED_FILTER_OPERATOR_ENDSWITH; break; - case 6: // FEED_FILTER_OPERATOR_CONTAINS - op = FEED_FILTER_OPERATOR_CONTAINS; break; - case 7: // FEED_FILTER_OPERATOR_CONTAINSNOT - op = FEED_FILTER_OPERATOR_NOTCONTAINS; break; - } - - wstring value = condition.attribute(L"value").value(); - - Aggregator.filter_manager.filters.back().AddCondition(element, op, value); - } - } - - // Fix default filters - foreach_(filter, Aggregator.filter_manager.filters) { - if (InStr(filter->name, L"Select new episodes only") > -1) { - filter->name = L"Discard watched and available episodes"; - filter->action = FEED_FILTER_ACTION_DISCARD; - filter->match = FEED_FILTER_MATCH_ANY; - foreach_(condition, filter->conditions) { - switch (condition->element) { - case FEED_FILTER_ELEMENT_EPISODE_NUMBER: - condition->op = FEED_FILTER_OPERATOR_ISLESSTHANOREQUALTO; - break; - case FEED_FILTER_ELEMENT_LOCAL_EPISODE_AVAILABLE: - condition->value = L"True"; - break; - } - } - } - } - - // Fix fansub filters - foreach_(filter, Aggregator.filter_manager.filters) { - if (InStr(filter->name, L"[Fansub]") > -1) { - filter->action = FEED_FILTER_ACTION_PREFER; - } - } - - LOG(LevelWarning, L"Torrent filters are converted."); - - // Display notice - win32::TaskDialog dlg(APP_TITLE, TD_ICON_INFORMATION); - dlg.SetMainInstruction(L"A friendly notice for torrent downloaders"); - wstring content = - L"In this update, torrent filters work a bit differently. Basically, all torrents are now in a blank state by default, " - L"then they're selected or discarded via filters. Before this update, they were all selected by default, and filters were used to discard them.\n\n" - L"Taiga did her best at converting your old filters to the new format, but you should still review them in case something's wrong. " - L"If you have any questions or something else to share, let us know through our MyAnimeList club.\n\n"; - if (!Account.MAL.user.empty()) { - content += L"Thank you, " + Account.MAL.user + L", for participating in the beta!"; - } else { - content += L"Thank you all for participating in the beta!"; - } - dlg.SetContent(content.c_str()); - dlg.AddButton(L"OK", IDOK); - dlg.Show(nullptr); - } - - // Make sure torrent downloading options are right - if (Meta.Version.revision < 248) { - RSS.Torrent.use_folder = RSS.Torrent.create_folder; - } - - // Load torrent archive - if (Meta.Version.revision < 250) { - Aggregator.file_archive.clear(); - PopulateFiles(Aggregator.file_archive, Taiga.GetDataPath() + L"feed\\", L"torrent", true, true); - } - - // Set default torrent filter options - if (Meta.Version.revision < 259) { - foreach_(filter, Aggregator.filter_manager.filters) { - if (filter->option = -1) { - if (filter->action == FEED_FILTER_ACTION_DISCARD && - !filter->conditions.empty() && - filter->conditions.front().element == FEED_FILTER_ELEMENT_META_ID && - filter->conditions.front().op == FEED_FILTER_OPERATOR_EQUALS && - filter->conditions.front().value.empty()) { - filter->option = FEED_FILTER_OPTION_DEACTIVATE; - } else { - filter->option = FEED_FILTER_OPTION_DEFAULT; - } - } - } - } -} - -void Settings::RestoreDefaults() { - // Take a backup - wstring backup = file_ + L".bak"; - DWORD flags = MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH; - MoveFileEx(file_.c_str(), backup.c_str(), flags); - - // Reload settings - wstring previous_user = Account.MAL.user; - wstring previous_theme = Program.General.theme; - Load(); - ApplyChanges(previous_user, previous_theme); - - // Reload settings dialog - if (SettingsDialog.IsWindow()) { - SettingsDialog.Destroy(); - ExecuteAction(L"Settings"); - } -} \ No newline at end of file diff --git a/settings.h b/settings.h deleted file mode 100644 index f6a4b58e7..000000000 --- a/settings.h +++ /dev/null @@ -1,165 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef SETTINGS_H -#define SETTINGS_H - -#include "std.h" -#include "feed.h" -#include "optional.h" - -// ============================================================================= - -class Settings { -public: - bool Load(); - bool Save(); - void ApplyChanges(const wstring& previous_user, const wstring& previous_theme); - void HandleCompatibility(); - void RestoreDefaults(); - - // Meta - struct Meta { - struct Version { - int major, minor, revision; - } Version; - } Meta; - - // Account - struct Account { - // MyAnimeList - struct Mal { - BOOL auto_sync; - wstring password, user; - } MAL; - // Update - struct Update { - int delay; - BOOL ask_to_confirm, check_player, go_to_nowplaying, out_of_range, out_of_root, wait_mp; - } Update; - } Account; - - // Folders - struct Folders { - vector root; - BOOL watch_enabled; - } Folders; - - // Announcements - struct Announce { - // HTTP - struct Http { - BOOL enabled; - wstring format, url; - } HTTP; - // Messenger - struct Msn { - BOOL enabled; - wstring format; - } MSN; - // mIRC - struct Mirc { - BOOL enabled, multi_server, use_action; - int mode; - wstring channels, format, service; - } MIRC; - // Skype - struct Skype { - BOOL enabled; - wstring format; - } Skype; - // Twitter - struct Twitter { - BOOL enabled; - wstring format, oauth_key, oauth_secret, user; - } Twitter; - } Announce; - - // Program - struct Program { - // General - struct General { - BOOL hide_sidebar; - BOOL enable_recognition, enable_sharing, enable_sync; - BOOL auto_start, close, minimize; - wstring external_links, theme; - } General; - // Position - struct Position { - int x, y, w, h; - BOOL maximized; - } Position; - // Start-up - struct StartUp { - BOOL check_new_episodes, check_new_version, minimize; - } StartUp; - // Exit - struct Exit { - BOOL remember_pos_size; - } Exit; - // Proxy - struct Proxy { - wstring host, password, user; - } Proxy; - // List - struct List { - int double_click, middle_click; - BOOL english_titles; - BOOL highlight; - BOOL progress_show_aired; - BOOL progress_show_available; - int sort_column; - int sort_order; - } List; - // Notifications - struct Notifications { - BOOL recognized, notrecognized; - wstring format; - } Notifications; - } Program; - - // Recognition - struct Recognition { - // Streaming - struct Streaming { - bool ann_enabled, crunchyroll_enabled, veoh_enabled, - viz_enabled, youtube_enabled; - } Streaming; - } Recognition; - - // RSS - struct Rss { - // Torrent - struct Torrent { - BOOL check_enabled, create_folder, set_folder, use_folder; - int app_mode, check_interval, new_action; - wstring app_path, download_path, search_url, source; - struct Filters { - BOOL global_enabled; - int archive_maxcount; - } Filters; - } Torrent; - } RSS; - -private: - wstring file_, folder_; -}; - -extern Settings Settings; - -#endif // SETTINGS_H \ No newline at end of file diff --git a/setup/Taiga.nsi b/setup/Taiga.nsi index 721ceefcb..fb410ede6 100644 Binary files a/setup/Taiga.nsi and b/setup/Taiga.nsi differ diff --git a/setup/bitmap/wizard.bmp b/setup/bitmap/wizard.bmp index 1c8293450..5bf6502d5 100644 Binary files a/setup/bitmap/wizard.bmp and b/setup/bitmap/wizard.bmp differ diff --git a/accessibility.cpp b/src/base/accessibility.cpp similarity index 55% rename from accessibility.cpp rename to src/base/accessibility.cpp index b6e15b7bf..fbd889147 100644 --- a/accessibility.cpp +++ b/src/base/accessibility.cpp @@ -1,221 +1,281 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" -#include "accessibility.h" - -// ============================================================================= - -AccessibleObject::AccessibleObject() - : acc_(nullptr), - win_event_hook_(nullptr) { -} - -AccessibleObject::~AccessibleObject() { - Unhook(); - Release(); -} - -HRESULT AccessibleObject::FromWindow(HWND hwnd, DWORD object_id) { - if (hwnd == nullptr) return E_INVALIDARG; - - Release(); - - return AccessibleObjectFromWindow(hwnd, object_id, IID_IAccessible, (void**)&acc_); -} - -HRESULT AccessibleObject::GetName(wstring& name, long child_id, IAccessible* acc) { - if (acc == nullptr) acc = acc_; - if (acc == nullptr) return E_INVALIDARG; - - BSTR buffer; - VARIANT var_child; - var_child.vt = VT_I4; - var_child.lVal = child_id; - - HRESULT hr = acc->get_accName(var_child, &buffer); - if (buffer) { - name = buffer; - } else { - name.clear(); - } - SysFreeString(buffer); - - return hr; -} - -HRESULT AccessibleObject::GetRole(wstring& role, long child_id, IAccessible* acc) { - if (acc == nullptr) acc = acc_; - if (acc == nullptr) return E_INVALIDARG; - - VARIANT var_child, var_result; - var_child.vt = VT_I4; - var_child.lVal = child_id; - - HRESULT hr = acc->get_accRole(var_child, &var_result); - - if (hr == S_OK && var_result.vt == VT_I4) { - DWORD role_id = var_result.lVal; - UINT role_length = GetRoleText(role_id, NULL, 0); - LPTSTR buffer = (LPTSTR)malloc((role_length + 1) * sizeof(TCHAR)); - - if (buffer != nullptr) { - GetRoleText(role_id, buffer, role_length + 1); - if (buffer) { - role = buffer; - } else { - role.clear(); - } - free(buffer); - } else { - return E_OUTOFMEMORY; - } - } - - return S_OK; -} - -HRESULT AccessibleObject::GetValue(wstring& value, long child_id, IAccessible* acc) { - if (acc == nullptr) acc = acc_; - if (acc == nullptr) return E_INVALIDARG; - - BSTR buffer; - VARIANT var_child; - var_child.vt = VT_I4; - var_child.lVal = child_id; - - HRESULT hr = acc->get_accValue(var_child, &buffer); - if (buffer) { - value = buffer; - } else { - value.clear(); - } - SysFreeString(buffer); - - return hr; -} - -HWINEVENTHOOK AccessibleObject::Hook(DWORD eventMin, DWORD eventMax, - HMODULE hmodWinEventProc, WINEVENTPROC pfnWinEventProc, - DWORD idProcess, DWORD idThread, DWORD dwFlags) { - Unhook(); - - win_event_hook_ = SetWinEventHook(eventMin, eventMax, hmodWinEventProc, - pfnWinEventProc, idProcess, idThread, dwFlags); - - return win_event_hook_; -} - -bool AccessibleObject::IsHooked() { - return win_event_hook_ != nullptr; -} - -void AccessibleObject::Unhook() { - if (win_event_hook_ != nullptr) { - UnhookWinEvent(win_event_hook_); - win_event_hook_ = nullptr; - } -} - -void AccessibleObject::Release() { - if (acc_ != nullptr) { - acc_->Release(); - acc_ = nullptr; - } -} - -HRESULT AccessibleObject::GetChildCount(long* child_count, IAccessible* acc) { - if (acc == nullptr) acc = acc_; - if (acc == nullptr) return E_INVALIDARG; - - return acc->get_accChildCount(child_count); -} - -HRESULT AccessibleObject::BuildChildren(vector& children, IAccessible* acc, LPARAM param) { - if (acc == nullptr) acc = acc_; - if (acc == nullptr) return E_INVALIDARG; - - long child_count, obtained_count; - - HRESULT hr = acc->get_accChildCount(&child_count); - if (FAILED(hr)) return hr; - if (child_count == 0) return S_FALSE; - - vector var_array(child_count); - hr = AccessibleChildren(acc, 0L, child_count, var_array.data(), &obtained_count); - if (FAILED(hr)) return hr; - - children.resize(obtained_count); - for (int i = 0; i < obtained_count; i++) { - VARIANT var_child = var_array[i]; - - if (var_child.vt == VT_DISPATCH) { - IDispatch* dispatch = var_child.pdispVal; - IAccessible* child = nullptr; - hr = dispatch->QueryInterface(IID_IAccessible, (void**)&child); - if (hr == S_OK) { - GetName(children.at(i).name, CHILDID_SELF, child); - GetRole(children.at(i).role, CHILDID_SELF, child); - GetValue(children.at(i).value, CHILDID_SELF, child); - if (AllowChildTraverse(children.at(i), param)) { - BuildChildren(children.at(i).children, child, param); - } - child->Release(); - } - dispatch->Release(); - - } else { - GetName(children.at(i).name, var_child.lVal, acc); - GetRole(children.at(i).role, var_child.lVal, acc); - GetValue(children.at(i).value, var_child.lVal, acc); - } - } - - return S_OK; -} - -bool AccessibleObject::AllowChildTraverse(AccessibleChild& child, LPARAM param) { - return true; -} - -// ============================================================================= - -void CALLBACK WinEventFunc(HWINEVENTHOOK hook, DWORD dwEvent, HWND hwnd, - LONG idObject, LONG idChild, - DWORD dwEventThread, DWORD dwmsEventTime) { - IAccessible* acc = nullptr; - VARIANT var_child; - HRESULT hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &acc, &var_child); - - if (hr == S_OK && acc != nullptr) { - BSTR name; - acc->get_accName(var_child, &name); - - switch (dwEvent) { - case EVENT_SYSTEM_FOREGROUND: - case EVENT_SYSTEM_ALERT: - case EVENT_OBJECT_FOCUS: - case EVENT_OBJECT_SELECTION: - case EVENT_OBJECT_VALUECHANGE: - break; - } - - SysFreeString(name); - acc->Release(); - } -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "accessibility.h" + +namespace base { + +AccessibleChild::AccessibleChild() + : role(0) { +} + +AccessibleObject::AccessibleObject() + : acc_(nullptr), + win_event_hook_(nullptr) { +} + +AccessibleObject::~AccessibleObject() { +#ifdef _DEBUG + Unhook(); +#endif + Release(); +} + +//////////////////////////////////////////////////////////////////////////////// + +HRESULT AccessibleObject::FromWindow(HWND hwnd, DWORD object_id) { + if (hwnd == nullptr) + return E_INVALIDARG; + + Release(); + + return AccessibleObjectFromWindow(hwnd, object_id, IID_IAccessible, + (void**)&acc_); +} + +void AccessibleObject::Release() { + if (acc_ != nullptr) { + acc_->Release(); + acc_ = nullptr; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +HRESULT AccessibleObject::GetName(std::wstring& name, long child_id, + IAccessible* acc) { + if (acc == nullptr) + acc = acc_; + if (acc == nullptr) + return E_INVALIDARG; + + VARIANT var_child; + var_child.vt = VT_I4; + var_child.lVal = child_id; + + BSTR buffer; + HRESULT hr = acc->get_accName(var_child, &buffer); + + if (buffer) { + name = buffer; + } else { + name.clear(); + } + SysFreeString(buffer); + + return hr; +} + +HRESULT AccessibleObject::GetRole(DWORD& role, long child_id, + IAccessible* acc) { + if (acc == nullptr) + acc = acc_; + if (acc == nullptr) + return E_INVALIDARG; + + VARIANT var_child; + var_child.vt = VT_I4; + var_child.lVal = child_id; + + VARIANT var_result; + HRESULT hr = acc->get_accRole(var_child, &var_result); + + if (hr == S_OK && var_result.vt == VT_I4) { + role = var_result.lVal; + return S_OK; + } + + return E_FAIL; +} + +HRESULT AccessibleObject::GetRole(std::wstring& role, long child_id, + IAccessible* acc) { + DWORD role_id = 0; + + HRESULT hr = GetRole(role_id, child_id, acc); + + if (hr != S_OK) + return hr; + + UINT role_length = GetRoleText(role_id, nullptr, 0); + LPTSTR buffer = (LPTSTR)malloc((role_length + 1) * sizeof(TCHAR)); + + if (buffer != nullptr) { + GetRoleText(role_id, buffer, role_length + 1); + if (buffer) { + role = buffer; + } else { + role.clear(); + } + free(buffer); + } else { + return E_OUTOFMEMORY; + } + + return S_OK; +} + +HRESULT AccessibleObject::GetValue(std::wstring& value, long child_id, + IAccessible* acc) { + if (acc == nullptr) + acc = acc_; + if (acc == nullptr) + return E_INVALIDARG; + + VARIANT var_child; + var_child.vt = VT_I4; + var_child.lVal = child_id; + + BSTR buffer; + HRESULT hr = acc->get_accValue(var_child, &buffer); + + if (buffer) { + value = buffer; + } else { + value.clear(); + } + SysFreeString(buffer); + + return hr; +} + +//////////////////////////////////////////////////////////////////////////////// + +HRESULT AccessibleObject::GetChildCount(long* child_count, IAccessible* acc) { + if (acc == nullptr) + acc = acc_; + if (acc == nullptr) + return E_INVALIDARG; + + return acc->get_accChildCount(child_count); +} + +HRESULT AccessibleObject::BuildChildren(std::vector& children, + IAccessible* acc, LPARAM param) { + if (acc == nullptr) + acc = acc_; + if (acc == nullptr) + return E_INVALIDARG; + + long child_count = 0; + HRESULT hr = acc->get_accChildCount(&child_count); + + if (FAILED(hr)) + return hr; + if (child_count == 0) + return S_FALSE; + + long obtained_count = 0; + std::vector var_array(child_count); + hr = AccessibleChildren(acc, 0L, child_count, var_array.data(), + &obtained_count); + + if (FAILED(hr)) + return hr; + + children.resize(obtained_count); + for (int i = 0; i < obtained_count; i++) { + VARIANT var_child = var_array[i]; + + if (var_child.vt == VT_DISPATCH) { + IDispatch* dispatch = var_child.pdispVal; + IAccessible* child = nullptr; + hr = dispatch->QueryInterface(IID_IAccessible, (void**)&child); + if (hr == S_OK) { + GetName(children.at(i).name, CHILDID_SELF, child); + GetRole(children.at(i).role, CHILDID_SELF, child); + GetValue(children.at(i).value, CHILDID_SELF, child); + if (AllowChildTraverse(children.at(i), param)) + BuildChildren(children.at(i).children, child, param); + child->Release(); + } + dispatch->Release(); + + } else { + GetName(children.at(i).name, var_child.lVal, acc); + GetRole(children.at(i).role, var_child.lVal, acc); + GetValue(children.at(i).value, var_child.lVal, acc); + } + } + + return S_OK; +} + +bool AccessibleObject::AllowChildTraverse(AccessibleChild& child, + LPARAM param) { + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +#ifdef _DEBUG +HWINEVENTHOOK AccessibleObject::Hook(DWORD eventMin, DWORD eventMax, + HMODULE hmodWinEventProc, + WINEVENTPROC pfnWinEventProc, + DWORD idProcess, DWORD idThread, + DWORD dwFlags) { + Unhook(); + + win_event_hook_ = SetWinEventHook(eventMin, eventMax, + hmodWinEventProc, + pfnWinEventProc, + idProcess, idThread, + dwFlags); + + return win_event_hook_; +} + +bool AccessibleObject::IsHooked() { + return win_event_hook_ != nullptr; +} + +void AccessibleObject::Unhook() { + if (win_event_hook_ != nullptr) { + UnhookWinEvent(win_event_hook_); + win_event_hook_ = nullptr; + } +} + +void CALLBACK WinEventFunc(HWINEVENTHOOK hook, DWORD dwEvent, HWND hwnd, + LONG idObject, LONG idChild, + DWORD dwEventThread, DWORD dwmsEventTime) { + IAccessible* acc = nullptr; + VARIANT var_child; + HRESULT hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &acc, + &var_child); + + if (hr == S_OK && acc != nullptr) { + BSTR name; + acc->get_accName(var_child, &name); + + switch (dwEvent) { + case EVENT_SYSTEM_FOREGROUND: + case EVENT_SYSTEM_ALERT: + case EVENT_OBJECT_FOCUS: + case EVENT_OBJECT_SELECTION: + case EVENT_OBJECT_VALUECHANGE: + break; + } + + SysFreeString(name); + acc->Release(); + } +} +#endif + +} // namespace base \ No newline at end of file diff --git a/src/base/accessibility.h b/src/base/accessibility.h new file mode 100644 index 000000000..149ce94b4 --- /dev/null +++ b/src/base/accessibility.h @@ -0,0 +1,77 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_ACCESSIBILITY_H +#define TAIGA_BASE_ACCESSIBILITY_H + +#include +#include +#include + +namespace base { + +struct AccessibleChild { + AccessibleChild(); + + std::wstring name; + DWORD role; + std::wstring value; + std::vector children; +}; + +class AccessibleObject { +public: + AccessibleObject(); + virtual ~AccessibleObject(); + + HRESULT FromWindow(HWND hwnd, DWORD object_id = OBJID_CLIENT); + void Release(); + + HRESULT GetName(std::wstring& name, long child_id = CHILDID_SELF, + IAccessible* acc = nullptr); + HRESULT GetRole(DWORD& role, long child_id = CHILDID_SELF, + IAccessible* acc = nullptr); + HRESULT GetRole(std::wstring& role, long child_id = CHILDID_SELF, + IAccessible* acc = nullptr); + HRESULT GetValue(std::wstring& value, long child_id = CHILDID_SELF, + IAccessible* acc = nullptr); + + HRESULT BuildChildren(std::vector& children, + IAccessible* acc = nullptr, LPARAM param = 0L); + HRESULT GetChildCount(long* child_count, IAccessible* acc = nullptr); + + virtual bool AllowChildTraverse(AccessibleChild& child, LPARAM param = 0L); + +#ifdef _DEBUG + HWINEVENTHOOK Hook(DWORD eventMin, DWORD eventMax, + HMODULE hmodWinEventProc, WINEVENTPROC pfnWinEventProc, + DWORD idProcess, DWORD idThread, DWORD dwFlags); + bool IsHooked(); + void Unhook(); +#endif + + std::vector children; + +private: + IAccessible* acc_; + HWINEVENTHOOK win_event_hook_; +}; + +} // namespace base + +#endif // TAIGA_BASE_ACCESSIBILITY_H \ No newline at end of file diff --git a/src/base/base64.cpp b/src/base/base64.cpp new file mode 100644 index 000000000..4e6e84ce9 --- /dev/null +++ b/src/base/base64.cpp @@ -0,0 +1,64 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include + +#include + +#include "base64.h" +#include "string.h" + +std::wstring Base64Decode(const std::string& str, bool for_filename) { + if (str.empty()) + return EmptyString(); + + Base64Coder coder; + coder.Decode((BYTE*)str.c_str(), str.size()); + + if (for_filename) { + std::wstring msg = StrToWstr(coder.DecodedMessage()); + ReplaceChar(msg, '-', '/'); + return msg; + } else { + return StrToWstr(coder.DecodedMessage()); + } +} + +std::wstring Base64Decode(const std::wstring& str, bool for_filename) { + return Base64Decode(WstrToStr(str), for_filename); +} + +std::wstring Base64Encode(const std::string& str, bool for_filename) { + if (str.empty()) + return EmptyString(); + + Base64Coder coder; + coder.Encode((BYTE*)str.c_str(), str.size()); + + if (for_filename) { + std::wstring msg = StrToWstr(coder.EncodedMessage()); + ReplaceChar(msg, '/', '-'); + return msg; + } else { + return StrToWstr(coder.EncodedMessage()); + } +} + +std::wstring Base64Encode(const std::wstring& str, bool for_filename) { + return Base64Encode(WstrToStr(str), for_filename); +} \ No newline at end of file diff --git a/src/base/base64.h b/src/base/base64.h new file mode 100644 index 000000000..ced6c6503 --- /dev/null +++ b/src/base/base64.h @@ -0,0 +1,29 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_BASE64_H +#define TAIGA_BASE_BASE64_H + +#include + +std::wstring Base64Decode(const std::string& str, bool for_filename = false); +std::wstring Base64Decode(const std::wstring& str, bool for_filename = false); +std::wstring Base64Encode(const std::string& str, bool for_filename = false); +std::wstring Base64Encode(const std::wstring& str, bool for_filename = false); + +#endif // TAIGA_BASE_BASE64_H \ No newline at end of file diff --git a/src/base/comparable.h b/src/base/comparable.h new file mode 100644 index 000000000..6829c6f4d --- /dev/null +++ b/src/base/comparable.h @@ -0,0 +1,58 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_COMPARE_H +#define TAIGA_BASE_COMPARE_H + +namespace base { + +enum CompareResult { + kLessThan = -1, + kEqualTo = 0, + kGreaterThan = 1, +}; + +template +class Comparable { +public: + bool operator==(const T& rhs) const { + return Compare(rhs) == kEqualTo; + } + bool operator!=(const T& rhs) const { + return !operator==(rhs); + } + bool operator<(const T& rhs) const { + return Compare(rhs) == kLessThan; + } + bool operator<=(const T& rhs) const { + return !operator>(rhs); + } + bool operator>(const T& rhs) const { + return Compare(rhs) == kGreaterThan; + } + bool operator>=(const T& rhs) const { + return !operator<(rhs); + } + +private: + virtual CompareResult Compare(const T& rhs) const = 0; +}; + +} // namespace base + +#endif // TAIGA_BASE_COMPARE_H \ No newline at end of file diff --git a/src/base/crc.cpp b/src/base/crc.cpp new file mode 100644 index 000000000..19a54a32f --- /dev/null +++ b/src/base/crc.cpp @@ -0,0 +1,73 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include + +#include + +#include "crc.h" +#include "string.h" + +std::wstring ConvertCrcValueToString(ULONG crc) { + wchar_t crc_val[16] = {0}; + _ultow_s(crc, crc_val, 16, 16); + + std::wstring value = crc_val; + + if (value.length() < 8) + value.insert(0, 8 - value.length(), '0'); + + ToUpper(value); + + return value; +} + +std::wstring CalculateCrcFromFile(const std::wstring& file) { + BYTE buffer[0x10000]; + DWORD bytes_read = 0; + + HANDLE file_handle = CreateFile( + file.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr); + + if (file_handle == INVALID_HANDLE_VALUE) + return std::wstring(); + + ULONG crc = crc32(0L, Z_NULL, 0); + BOOL success = ReadFile(file_handle, buffer, sizeof(buffer), + &bytes_read, nullptr); + while (success && bytes_read) { + crc = crc32(crc, buffer, bytes_read); + success = ReadFile(file_handle, buffer, sizeof(buffer), + &bytes_read, nullptr); + } + + if (file_handle != nullptr) + CloseHandle(file_handle); + + return ConvertCrcValueToString(crc); +} + +std::wstring CalculateCrcFromString(const std::wstring& str) { + std::string text = WstrToStr(str); + + ULONG crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, reinterpret_cast(text.data()), text.size()); + + return ConvertCrcValueToString(crc); +} \ No newline at end of file diff --git a/src/base/crc.h b/src/base/crc.h new file mode 100644 index 000000000..35ebc0675 --- /dev/null +++ b/src/base/crc.h @@ -0,0 +1,27 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_CRC_H +#define TAIGA_BASE_CRC_H + +#include + +std::wstring CalculateCrcFromFile(const std::wstring& file); +std::wstring CalculateCrcFromString(const std::wstring& str); + +#endif // TAIGA_BASE_CRC_H \ No newline at end of file diff --git a/src/base/crypto.cpp b/src/base/crypto.cpp new file mode 100644 index 000000000..3bd1efac3 --- /dev/null +++ b/src/base/crypto.cpp @@ -0,0 +1,143 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include +#include + +#include "crypto.h" +#include "string.h" + +namespace base { +const wchar_t encryption_char = L'?'; +const wchar_t* encryption_key = L"Tenori Taiga"; +const size_t encryption_length_min = 16; + +std::wstring Xor(std::wstring str, const std::wstring& key) { + for (size_t i = 0; i < str.size(); i++) + str[i] = str[i] ^ key[i % key.length()]; + + return str; +} + +} // namespace base + +std::wstring SimpleEncrypt(std::wstring str) { + // Set minimum length + if (str.length() > 0 && str.length() < base::encryption_length_min) + str.append(base::encryption_length_min - str.length(), + base::encryption_char); + + // Encrypt + str = base::Xor(str, base::encryption_key); + + // Convert to hexadecimal string + std::wstring buffer; + for (size_t i = 0; i < str.size(); i++) { + wchar_t c[32] = {'\0'}; + _itow_s(str[i], c, 32, 16); + if (wcslen(c) == 1) + buffer.push_back('0'); + buffer += c; + } + ToUpper(buffer); + + return buffer; +} + +std::wstring SimpleDecrypt(std::wstring str) { + // Convert from hexadecimal string + std::wstring buffer; + for (size_t i = 0; i < str.size(); i = i + 2) { + wchar_t c = static_cast(wcstoul(str.substr(i, 2).c_str(), + nullptr, 16)); + buffer.push_back(c); + } + + // Decrypt + buffer = base::Xor(buffer, base::encryption_key); + + // Trim characters appended to match the minimum length + if (buffer.size() >= base::encryption_length_min) + TrimRight(buffer, std::wstring(1, base::encryption_char).c_str()); + + return buffer; +} + +//////////////////////////////////////////////////////////////////////////////// +// HMAC code is based on "Creating an HMAC" example +// http://msdn.microsoft.com/en-us/library/aa382379%28v=VS.85%29.aspx +// +// Key creation is based on "Crypto wrapper for Microsoft CryptoAPI" +// Copyright (c) 2005-2009, Jouni Malinen +// http://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/external/bsd/wpa/dist/src/crypto/crypto_cryptoapi.c + +std::string HmacSha1(const std::string& key_bytes, const std::string& data) { + std::string hash; + + HCRYPTPROV hProv = NULL; + HCRYPTHASH hHash = NULL; + HCRYPTHASH hHmac = NULL; + HCRYPTKEY hKey = NULL; + PBYTE pbHash = nullptr; + DWORD dwDataLen = 0; + + HMAC_INFO HmacInfo; + ZeroMemory(&HmacInfo, sizeof(HmacInfo)); + HmacInfo.HashAlgid = CALG_SHA1; + + if (CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + if (CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) { + if (CryptHashData(hHash, (BYTE*)key_bytes.c_str(), key_bytes.size(), 0)) { + const size_t key_size = 1024; + struct { + BLOBHEADER hdr; + DWORD len; + BYTE key[key_size]; + } key_blob; + key_blob.hdr.bType = PLAINTEXTKEYBLOB; + key_blob.hdr.bVersion = CUR_BLOB_VERSION; + key_blob.hdr.reserved = 0; + key_blob.hdr.aiKeyAlg = CALG_RC2; + key_blob.len = key_bytes.size(); + ZeroMemory(key_blob.key, sizeof(key_blob.key)); + CopyMemory(key_blob.key, key_bytes.c_str(), min(key_bytes.size(), key_size)); + if (CryptImportKey(hProv, (BYTE*)&key_blob, sizeof(key_blob), 0, CRYPT_IPSEC_HMAC_KEY, &hKey)) { + if (CryptCreateHash(hProv, CALG_HMAC, hKey, 0, &hHmac)) { + if (CryptSetHashParam(hHmac, HP_HMAC_INFO, (BYTE*)&HmacInfo, 0)) { + if (CryptHashData(hHmac, (BYTE*)data.c_str(), data.size(), 0)) { + if (CryptGetHashParam(hHmac, HP_HASHVAL, nullptr, &dwDataLen, 0)) { + pbHash = (BYTE*)malloc(dwDataLen); + if (pbHash != nullptr) { + if (CryptGetHashParam(hHmac, HP_HASHVAL, pbHash, &dwDataLen, 0)) { + for (DWORD i = 0 ; i < dwDataLen ; i++) { + hash.push_back((char)pbHash[i]); + } } } } } } } } } } } + + if (hHmac) + CryptDestroyHash(hHmac); + if (hKey) + CryptDestroyKey(hKey); + if (hHash) + CryptDestroyHash(hHash); + if (hProv) + CryptReleaseContext(hProv, 0); + if (pbHash) + free(pbHash); + + return hash; +} \ No newline at end of file diff --git a/src/base/crypto.h b/src/base/crypto.h new file mode 100644 index 000000000..d118d59eb --- /dev/null +++ b/src/base/crypto.h @@ -0,0 +1,29 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_CRYPTO_H +#define TAIGA_BASE_CRYPTO_H + +#include + +std::wstring SimpleEncrypt(std::wstring str); +std::wstring SimpleDecrypt(std::wstring str); + +std::string HmacSha1(const std::string& key_bytes, const std::string& data); + +#endif // TAIGA_BASE_CRYPTO_H \ No newline at end of file diff --git a/src/base/file.cpp b/src/base/file.cpp new file mode 100644 index 000000000..61142ff9f --- /dev/null +++ b/src/base/file.cpp @@ -0,0 +1,379 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include +#include + +#include "file.h" +#include "string.h" +#include "win/win_registry.h" + +#define MAKEQWORD(a, b) ((QWORD)(((QWORD)((DWORD)(a))) << 32 | ((DWORD)(b)))) + +//////////////////////////////////////////////////////////////////////////////// + +HANDLE OpenFileForGenericRead(const std::wstring& path) { + return ::CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); +} + +HANDLE OpenFileForGenericWrite(const std::wstring& path) { + return ::CreateFile(path.c_str(), GENERIC_WRITE, 0, nullptr, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); +} + +//////////////////////////////////////////////////////////////////////////////// + +unsigned long GetFileAge(const std::wstring& path) { + HANDLE file_handle = OpenFileForGenericRead(path); + + if (file_handle == INVALID_HANDLE_VALUE) + return 0; + + // Get the time the file was last modified + FILETIME ft_file; + BOOL result = GetFileTime(file_handle, nullptr, nullptr, &ft_file); + CloseHandle(file_handle); + + if (!result) + return 0; + + // Get current time + SYSTEMTIME st_now; + GetSystemTime(&st_now); + FILETIME ft_now; + SystemTimeToFileTime(&st_now, &ft_now); + + // Convert to ULARGE_INTEGER + ULARGE_INTEGER ul_file; + ul_file.LowPart = ft_file.dwLowDateTime; + ul_file.HighPart = ft_file.dwHighDateTime; + ULARGE_INTEGER ul_now; + ul_now.LowPart = ft_now.dwLowDateTime; + ul_now.HighPart = ft_now.dwHighDateTime; + + // Return difference in seconds + return static_cast( + (ul_now.QuadPart - ul_file.QuadPart) / 10000000); +} + +QWORD GetFileSize(const std::wstring& path) { + QWORD file_size = 0; + + HANDLE file_handle = OpenFileForGenericRead(path); + + if (file_handle != INVALID_HANDLE_VALUE) { + DWORD size_high = 0; + DWORD size_low = ::GetFileSize(file_handle, &size_high); + if (size_low != INVALID_FILE_SIZE) + file_size = MAKEQWORD(size_high, size_low); + CloseHandle(file_handle); + } + + return file_size; +} + +QWORD GetFolderSize(const std::wstring& path, bool recursive) { + QWORD folder_size = 0; + + WIN32_FIND_DATA find_data; + std::wstring file_name = path + L"*.*"; + HANDLE file_handle = FindFirstFile(file_name.c_str(), &find_data); + + if (file_handle == INVALID_HANDLE_VALUE) + return 0; + + QWORD max_dword = static_cast(MAXDWORD) + 1; + + do { + if (IsDirectory(find_data)) { + if (recursive && IsValidDirectory(find_data)) { + file_name = path + find_data.cFileName + L"\\"; + folder_size += GetFolderSize(file_name, recursive); + } + } else { + folder_size += static_cast(find_data.nFileSizeHigh) * max_dword + + static_cast(find_data.nFileSizeLow); + } + } while (FindNextFile(file_handle, &find_data)); + + FindClose(file_handle); + + return folder_size; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool Execute(const std::wstring& path, const std::wstring& parameters) { + if (path.empty()) + return false; + + HINSTANCE value = ShellExecute(nullptr, L"open", path.c_str(), + parameters.c_str(), nullptr, SW_SHOWNORMAL); + + return reinterpret_cast(value) > 32; +} + +BOOL ExecuteEx(const std::wstring& path, const std::wstring& parameters) { + SHELLEXECUTEINFO si = {0}; + si.cbSize = sizeof(SHELLEXECUTEINFO); + si.fMask = SEE_MASK_DOENVSUBST | SEE_MASK_FLAG_NO_UI | SEE_MASK_UNICODE; + si.lpVerb = L"open"; + si.lpFile = path.c_str(); + si.lpParameters = parameters.c_str(); + si.nShow = SW_SHOWNORMAL; + + return ShellExecuteEx(&si); +} + +void ExecuteLink(const std::wstring& link) { + ShellExecute(nullptr, nullptr, link.c_str(), nullptr, nullptr, SW_SHOWNORMAL); +} + +//////////////////////////////////////////////////////////////////////////////// + +bool CreateFolder(const std::wstring& path) { + return SHCreateDirectoryEx(nullptr, path.c_str(), nullptr) == ERROR_SUCCESS; +} + +int DeleteFolder(std::wstring path) { + if (path.back() == '\\') + path.pop_back(); + + path.push_back('\0'); + + SHFILEOPSTRUCT fos = {0}; + fos.wFunc = FO_DELETE; + fos.pFrom = path.c_str(); + fos.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT; + + return SHFileOperation(&fos); +} + +// Extends the length limit from 260 to 32767 characters +std::wstring GetExtendedLengthPath(const std::wstring& path) { + if (!StartsWith(path, L"\\\\?\\")) + return L"\\\\?\\" + path; + return path; +} + +bool IsDirectory(const WIN32_FIND_DATA& find_data) { + return (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; +} + +bool IsValidDirectory(const WIN32_FIND_DATA& find_data) { + return wcscmp(find_data.cFileName, L".") != 0 && + wcscmp(find_data.cFileName, L"..") != 0; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool FileExists(const std::wstring& file) { + if (file.empty()) + return false; + + HANDLE file_handle = OpenFileForGenericRead(file); + + if (file_handle != INVALID_HANDLE_VALUE) { + CloseHandle(file_handle); + return true; + } + + return false; +} + +bool FolderExists(const std::wstring& path) { + DWORD file_attr = GetFileAttributes(path.c_str()); + + return (file_attr != INVALID_FILE_ATTRIBUTES) && + (file_attr & FILE_ATTRIBUTE_DIRECTORY); +} + +bool PathExists(const std::wstring& path) { + return GetFileAttributes(path.c_str()) != INVALID_FILE_ATTRIBUTES; +} + +void ValidateFileName(std::wstring& file) { + EraseChars(file, L"\\/:*?<>|"); +} + +//////////////////////////////////////////////////////////////////////////////// + +std::wstring ExpandEnvironmentStrings(const std::wstring& path) { + WCHAR buff[MAX_PATH]; + + if (::ExpandEnvironmentStrings(path.c_str(), buff, MAX_PATH)) + return buff; + + return path; +} + +std::wstring GetDefaultAppPath(const std::wstring& extension, + const std::wstring& default_value) { + win::Registry reg; + reg.OpenKey(HKEY_CLASSES_ROOT, extension, 0, KEY_QUERY_VALUE); + + std::wstring path = reg.QueryValue(L""); + + if (!path.empty()) { + path += L"\\shell\\open\\command"; + reg.OpenKey(HKEY_CLASSES_ROOT, path, 0, KEY_QUERY_VALUE); + + path = reg.QueryValue(L""); + Replace(path, L"\"", L""); + Trim(path, L" %1"); + } + + reg.CloseKey(); + + return path.empty() ? default_value : path; +} + +//////////////////////////////////////////////////////////////////////////////// + +unsigned int PopulateFiles(std::vector& file_list, + const std::wstring& path, + const std::wstring& extension, + bool recursive, bool trim_extension) { + if (path.empty()) + return 0; + + WIN32_FIND_DATA find_data; + std::wstring file_name = path + L"*.*"; + HANDLE file_handle = FindFirstFile(file_name.c_str(), &find_data); + + if (file_handle == INVALID_HANDLE_VALUE) + return 0; + + unsigned int file_count = 0; + + do { + if (IsDirectory(find_data)) { + if (recursive && IsValidDirectory(find_data)) { + file_name = path + find_data.cFileName + L"\\"; + file_count += PopulateFiles(file_list, file_name, extension, recursive, + trim_extension); + } + } else { + if (extension.empty() || + IsEqual(GetFileExtension(find_data.cFileName), extension)) { + if (trim_extension) { + file_list.push_back(GetFileWithoutExtension(find_data.cFileName)); + } else { + file_list.push_back(find_data.cFileName); + } + file_count++; + } + } + } while (FindNextFile(file_handle, &find_data)); + + FindClose(file_handle); + + return file_count; +} + +int PopulateFolders(std::vector& folder_list, + const std::wstring& path) { + if (path.empty()) + return 0; + + WIN32_FIND_DATA find_data; + std::wstring file_name = path + L"*.*"; + HANDLE file_handle = FindFirstFile(file_name.c_str(), &find_data); + + if (file_handle == INVALID_HANDLE_VALUE) + return 0; + + int folder_count = 0; + + do { + if (IsDirectory(find_data) && IsValidDirectory(find_data)) { + folder_count++; + folder_list.push_back(find_data.cFileName); + } + } while (FindNextFile(file_handle, &find_data)); + + FindClose(file_handle); + + return folder_count; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool ReadFromFile(const std::wstring& path, std::string& output) { + std::ifstream is; + is.open(WstrToStr(path).c_str(), std::ios::binary); + + is.seekg(0, std::ios::end); + size_t len = static_cast(is.tellg()); + + if (len != -1) { + output.resize(len); + is.seekg(0, std::ios::beg); + is.read((char*)output.data(), output.size()); + } + + is.close(); + return len != -1; +} + +bool SaveToFile(LPCVOID data, DWORD length, const string_t& path, + bool take_backup) { + // Make sure the path is available + CreateFolder(GetPathOnly(path)); + + // Take a backup if needed + if (take_backup) { + std::wstring new_path = path + L".bak"; + MoveFileEx(path.c_str(), new_path.c_str(), + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH); + } + + // Save the data + BOOL result = FALSE; + HANDLE file_handle = OpenFileForGenericWrite(path); + if (file_handle != INVALID_HANDLE_VALUE) { + DWORD bytes_written = 0; + result = ::WriteFile(file_handle, data, length, &bytes_written, nullptr); + ::CloseHandle(file_handle); + } + + return result != FALSE; +} + +//////////////////////////////////////////////////////////////////////////////// + +std::wstring ToSizeString(QWORD qwSize) { + std::wstring size, unit; + + if (qwSize > 1073741824) { // 2^30 + size = ToWstr(static_cast(qwSize) / 1073741824, 2); + unit = L" GB"; + } else if (qwSize > 1048576) { // 2^20 + size = ToWstr(static_cast(qwSize) / 1048576, 2); + unit = L" MB"; + } else if (qwSize > 1024) { // 2^10 + size = ToWstr(static_cast(qwSize) / 1024, 2); + unit = L" KB"; + } else { + size = ToWstr(qwSize); + unit = L" bytes"; + } + + return size + unit; +} \ No newline at end of file diff --git a/src/base/file.h b/src/base/file.h new file mode 100644 index 000000000..586c0f107 --- /dev/null +++ b/src/base/file.h @@ -0,0 +1,80 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_FILE_H +#define TAIGA_BASE_FILE_H + +#include +#include +#include + +#include "types.h" + +unsigned long GetFileAge(const std::wstring& path); +QWORD GetFileSize(const std::wstring& path); +QWORD GetFolderSize(const std::wstring& path, bool recursive); + +bool Execute(const std::wstring& path, const std::wstring& parameters = L""); +BOOL ExecuteEx(const std::wstring& path, const std::wstring& parameters = L""); +void ExecuteLink(const std::wstring& link); + +bool CreateFolder(const std::wstring& path); +int DeleteFolder(std::wstring path); + +std::wstring GetExtendedLengthPath(const std::wstring& path); +bool IsDirectory(const WIN32_FIND_DATA& find_data); +bool IsValidDirectory(const WIN32_FIND_DATA& find_data); + +bool FileExists(const std::wstring& file); +bool FolderExists(const std::wstring& folder); +bool PathExists(const std::wstring& path); +void ValidateFileName(std::wstring& path); + +std::wstring ExpandEnvironmentStrings(const std::wstring& path); +std::wstring GetDefaultAppPath(const std::wstring& extension, const std::wstring& default_value); + +unsigned int PopulateFiles(std::vector& file_list, const std::wstring& path, const std::wstring& extension = L"", bool recursive = false, bool trim_extension = false); +int PopulateFolders(std::vector& folder_list, const std::wstring& path); + +bool ReadFromFile(const std::wstring& path, std::string& output); +bool SaveToFile(LPCVOID data, DWORD length, const std::wstring& path, bool take_backup = false); + +std::wstring ToSizeString(QWORD qwSize); + +class FileSearchHelper { +public: + FileSearchHelper(); + virtual ~FileSearchHelper() {} + + bool Search(const std::wstring& root); + + virtual bool OnDirectory(const std::wstring& root, const std::wstring& name) = 0; + virtual bool OnFile(const std::wstring& root, const std::wstring& name) = 0; + + void set_skip_directories(bool skip_directories); + void set_skip_files(bool skip_files); + void set_skip_subdirectories(bool skip_subdirectories); + +protected: + ULONGLONG minimum_file_size_; + bool skip_directories_; + bool skip_files_; + bool skip_subdirectories_; +}; + +#endif // TAIGA_BASE_FILE_H \ No newline at end of file diff --git a/src/base/file_search.cpp b/src/base/file_search.cpp new file mode 100644 index 000000000..bbddb79ad --- /dev/null +++ b/src/base/file_search.cpp @@ -0,0 +1,97 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "file.h" +#include "log.h" +#include "string.h" + +FileSearchHelper::FileSearchHelper() + : minimum_file_size_(0), + skip_directories_(false), + skip_files_(false), + skip_subdirectories_(false) { +} + +bool FileSearchHelper::Search(const std::wstring& root) { + if (root.empty()) + return false; + if (skip_directories_ && skip_files_) + return false; + + std::wstring path = AddTrailingSlash(GetExtendedLengthPath(root)) + L"*"; + bool result = false; + + WIN32_FIND_DATA find_data; + HANDLE handle = FindFirstFile(path.c_str(), &find_data); + + do { + if (handle == INVALID_HANDLE_VALUE) { + LOG(LevelError, Logger::FormatError(GetLastError())); + LOG(LevelError, L"Path: " + path); + SetLastError(ERROR_SUCCESS); + return false; + } + + // Directory + if (IsDirectory(find_data)) { + if (IsValidDirectory(find_data)) { + if (!skip_directories_) + result = OnDirectory(root, find_data.cFileName); + if (!skip_subdirectories_ && !result) + result = Search(AddTrailingSlash(root) + find_data.cFileName); + } + + // File + } else if (!skip_files_) { + if (find_data.nFileSizeLow < minimum_file_size_) { + LOG(LevelDebug, + L"File is ignored because its size does not meet the threshold."); + LOG(LevelDebug, + L"Path: " + AddTrailingSlash(root) + find_data.cFileName); + continue; + } + result = OnFile(root, find_data.cFileName); + } + + } while (!result && FindNextFile(handle, &find_data)); + + FindClose(handle); + return result; +} + +bool FileSearchHelper::OnDirectory(const std::wstring& root, + const std::wstring& name) { + return false; +} + +bool FileSearchHelper::OnFile(const std::wstring& root, + const std::wstring& name) { + return false; +} + +void FileSearchHelper::set_skip_directories(bool skip_directories) { + skip_directories_ = skip_directories; +} + +void FileSearchHelper::set_skip_files(bool skip_files) { + skip_files_ = skip_files; +} + +void FileSearchHelper::set_skip_subdirectories(bool skip_subdirectories) { + skip_subdirectories_ = skip_subdirectories; +} \ No newline at end of file diff --git a/foreach.h b/src/base/foreach.h similarity index 66% rename from foreach.h rename to src/base/foreach.h index 0af531187..fdd74e005 100644 --- a/foreach.h +++ b/src/base/foreach.h @@ -1,38 +1,38 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2013, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef FOREACH_H -#define FOREACH_H - -// iterator -#define foreach_(item, container) \ - for (auto item = (container).begin(); item != (container).end(); ++item) - -// reverse iterator -#define foreach_r_(item, container) \ - for (auto item = (container).rbegin(); item != (container).rend(); ++item) - -// const iterator -#define foreach_c_(item, container) \ - for (auto item = (container).cbegin(); item != (container).cend(); ++item) - -// const reverse iterator -#define foreach_cr_(item, container) \ - for (auto item = (container).crbegin(); item != (container).crend(); ++item) - -#endif // FOREACH_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_FOREACH_H +#define TAIGA_BASE_FOREACH_H + +// iterator +#define foreach_(item, container) \ + for (auto item = (container).begin(); item != (container).end(); ++item) + +// reverse iterator +#define foreach_r_(item, container) \ + for (auto item = (container).rbegin(); item != (container).rend(); ++item) + +// const iterator +#define foreach_c_(item, container) \ + for (auto item = (container).cbegin(); item != (container).cend(); ++item) + +// const reverse iterator +#define foreach_cr_(item, container) \ + for (auto item = (container).crbegin(); item != (container).crend(); ++item) + +#endif // TAIGA_BASE_FOREACH_H \ No newline at end of file diff --git a/gfx.cpp b/src/base/gfx.cpp similarity index 75% rename from gfx.cpp rename to src/base/gfx.cpp index 36b1afbd9..97b052993 100644 --- a/gfx.cpp +++ b/src/base/gfx.cpp @@ -1,259 +1,286 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "gfx.h" - -win32::GdiPlus GdiPlus; - -// ============================================================================= - -HFONT ChangeDCFont(HDC hdc, LPCWSTR lpFaceName, INT iSize, BOOL bBold, BOOL bItalic, BOOL bUnderline) { - HFONT hFont = reinterpret_cast(GetCurrentObject(hdc, OBJ_FONT)); - LOGFONT lFont; GetObject(hFont, sizeof(LOGFONT), &lFont); - - if (lpFaceName) - lstrcpy(lFont.lfFaceName, lpFaceName); - if (iSize > -1) { - lFont.lfHeight = -MulDiv(iSize, GetDeviceCaps(hdc, LOGPIXELSY), 72); - lFont.lfWidth = 0; - } - if (bBold > -1) - lFont.lfWeight = bBold ? FW_BOLD : FW_NORMAL; - if (bItalic > -1) - lFont.lfItalic = bItalic; - if (bUnderline > -1) - lFont.lfUnderline = bUnderline; - - hFont = CreateFontIndirect(&lFont); - return reinterpret_cast(SelectObject(hdc, hFont)); -} - -int GetTextHeight(HDC hdc) { - SIZE size = {0}; - GetTextExtentPoint32(hdc, L"T", 1, &size); - return size.cy; -} - -BOOL GradientRect(HDC hdc, const LPRECT lpRect, DWORD dwColor1, DWORD dwColor2, bool bVertical) { - TRIVERTEX vertex[2]; - vertex[0].x = lpRect->left; - vertex[0].y = lpRect->top; - vertex[0].Red = GetRValue(dwColor1) << 8; - vertex[0].Green = GetGValue(dwColor1) << 8; - vertex[0].Blue = GetBValue(dwColor1) << 8; - vertex[0].Alpha = 0x0000; - vertex[1].x = lpRect->right; - vertex[1].y = lpRect->bottom; - vertex[1].Red = GetRValue(dwColor2) << 8; - vertex[1].Green = GetGValue(dwColor2) << 8; - vertex[1].Blue = GetBValue(dwColor2) << 8; - vertex[1].Alpha = 0x0000; - GRADIENT_RECT gRect = {0, 1}; - return GdiGradientFill(hdc, vertex, 2, &gRect, 1, static_cast(bVertical)); -} - -BOOL DrawProgressBar(HDC hdc, const LPRECT lpRect, DWORD dwColor1, DWORD dwColor2, DWORD dwColor3) { - // Draw bottom rect - RECT rect = *lpRect; - rect.top += (rect.bottom - rect.top) / 2; - HBRUSH brush = CreateSolidBrush(dwColor3); - FillRect(hdc, &rect, brush); - DeleteObject(brush); - // Draw top gradient - rect.bottom = rect.top; - rect.top = lpRect->top; - return GradientRect(hdc, &rect, dwColor1, dwColor2, true); -} - -// ============================================================================= - -COLORREF HexToARGB(const wstring& text) { - int i = text.length() - 6; - if (i < 0) return 0; - - unsigned int r, g, b; - r = wcstoul(text.substr(i + 0, 2).c_str(), NULL, 16); - g = wcstoul(text.substr(i + 2, 2).c_str(), NULL, 16); - b = wcstoul(text.substr(i + 4, 2).c_str(), NULL, 16); - - return RGB(r, g, b); -} - -win32::Rect ResizeRect(const win32::Rect& rect_dest, int src_width, int src_height, bool stretch, bool center_x, bool center_y) { - win32::Rect rect = rect_dest; - - float dest_width = static_cast(rect_dest.Width()); - float dest_height = static_cast(rect_dest.Height()); - float image_width = static_cast(src_width); - float image_Height = static_cast(src_height); - - // Source < Destination (No need to resize) - if ((image_width < dest_width) && (image_Height < dest_height) && !stretch) { - rect.right = rect.left + src_width; - rect.bottom = rect.top + src_height; - if (center_x) rect.Offset(static_cast((image_width - image_width) / 2.0f), 0); - if (center_y) rect.Offset(0, static_cast((dest_height - image_Height) / 2.0f)); - - // Source > Destination (Resize) - } else { - // Calculate aspect ratios - float dest_ratio = dest_width / dest_height; - float image_ratio = image_width / image_Height; - - // Width > Height - if (image_ratio > dest_ratio) { - rect.bottom = rect.top + static_cast(dest_width * (1.0f / image_ratio)); - if (center_y) rect.Offset(0, static_cast((dest_height - rect.Height()) / 2.0f)); - // Height > Width - } else if (image_ratio < dest_ratio) { - rect.right = rect.left + static_cast(dest_height * image_ratio); - if (center_x) rect.Offset(static_cast((dest_width - rect.Width()) / 2.0f), 0); - } - } - - return rect; -} - -int ScaleX(int value) { - static int dpi_x = 0; - if (!dpi_x) { - HDC hdc = GetDC(NULL); - if (hdc) { - dpi_x = GetDeviceCaps(hdc, LOGPIXELSX); - ReleaseDC(NULL, hdc); - } - } - return MulDiv(value, dpi_x, 96); -} - -int ScaleY(int value) { - static int dpi_y = 0; - if (!dpi_y) { - HDC hdc = GetDC(NULL); - if (hdc) { - dpi_y = GetDeviceCaps(hdc, LOGPIXELSY); - ReleaseDC(NULL, hdc); - } - } - return MulDiv(value, dpi_y, 96); -} - -void RgbToHsv(float r, float g, float b, float& h, float& s, float& v) { - float rgb_min = min(r, min(g, b)); - float rgb_max = max(r, max(g, b)); - float rgb_delta = rgb_max - rgb_min; - - s = rgb_delta / (rgb_max + 1e-20f); - v = rgb_max; - - if (r == rgb_max) { - h = (g - b) / (rgb_delta + 1e-20f); - } else if (g == rgb_max) { - h = 2 + (b - r) / (rgb_delta + 1e-20f); - } else { - h = 4 + (r - g) / (rgb_delta + 1e-20f); - } - - if (h < 0) - h += 6.0f; - h *= (1.0f / 6.0f); -} - -void HsvToRgb(float& r, float& g, float& b, float h, float s, float v) { - if (s == 0) { - r = g = b = v; - return; - } - - h /= 60; - int i = static_cast(floor(h)); - float f = h - i; - float p = v * (1 - s); - float q = v * (1 - s * f); - float t = v * (1 - s * (1 - f)); - - switch (i) { - case 0: - r = v; g = t; b = p; - break; - case 1: - r = q; g = v; b = p; - break; - case 2: - r = p; g = v; b = t; - break; - case 3: - r = p; g = q; b = v; - break; - case 4: - r = t; g = p; b = v; - break; - default: - r = v; g = p; b = q; - break; - } -} - -COLORREF ChangeColorBrightness(COLORREF color, float modifier) { - float r = static_cast(GetRValue(color)) / 255.0f; - float g = static_cast(GetGValue(color)) / 255.0f; - float b = static_cast(GetBValue(color)) / 255.0f; - - float h, s, v; - - RgbToHsv(r, g, b, h, s, v); - - v += modifier; - if (v < 0.0f) v = 0.0f; - if (v > 1.0f) v = 1.0f; - - HsvToRgb(r, g, b, h, s, v); - - return RGB(static_cast(r * 255), - static_cast(g * 255), - static_cast(b * 255)); -} - -// ============================================================================= - -bool Image::Load(const wstring& path) { - ::DeleteObject(dc.DetachBitmap()); - - HBITMAP hbmp = nullptr; - if (dc.Get() == nullptr) { - HDC hScreen = ::GetDC(nullptr); - dc = ::CreateCompatibleDC(hScreen); - ::ReleaseDC(NULL, hScreen); - } - - Gdiplus::Bitmap bmp(path.c_str()); - rect.right = bmp.GetWidth(); - rect.bottom = bmp.GetHeight(); - bmp.GetHBITMAP(NULL, &hbmp); - - if (!hbmp || !rect.right || !rect.bottom) { - ::DeleteObject(hbmp); - ::DeleteDC(dc.DetachDC()); - return false; - } else { - dc.AttachBitmap(hbmp); - return true; - } +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include +#include + +#include "gfx.h" + +win::GdiPlus GdiPlus; + +namespace base { + +Image::Image() + : data(0) { +} + +bool Image::Load(const std::wstring& path) { + ::DeleteObject(dc.DetachBitmap()); + + if (dc.Get() == nullptr) { + HDC hScreen = ::GetDC(nullptr); + dc = ::CreateCompatibleDC(hScreen); + ::ReleaseDC(NULL, hScreen); + } + + Gdiplus::Bitmap bmp(path.c_str()); + rect.right = bmp.GetWidth(); + rect.bottom = bmp.GetHeight(); + + HBITMAP hbmp = nullptr; + bmp.GetHBITMAP(Gdiplus::Color::Transparent, &hbmp); + + if (!hbmp || !rect.right || !rect.bottom) { + ::DeleteObject(hbmp); + ::DeleteDC(dc.DetachDc()); + return false; + } + + dc.AttachBitmap(hbmp); + return true; +} + +} // namespace base + +//////////////////////////////////////////////////////////////////////////////// + +HFONT ChangeDCFont(HDC hdc, LPCWSTR lpFaceName, INT iSize, + BOOL bBold, BOOL bItalic, BOOL bUnderline) { + LOGFONT lFont; + HFONT hFont = reinterpret_cast(GetCurrentObject(hdc, OBJ_FONT)); + GetObject(hFont, sizeof(LOGFONT), &lFont); + + if (lpFaceName) + lstrcpy(lFont.lfFaceName, lpFaceName); + if (iSize > -1) { + lFont.lfHeight = -MulDiv(iSize, GetDeviceCaps(hdc, LOGPIXELSY), 72); + lFont.lfWidth = 0; + } + if (bBold > -1) + lFont.lfWeight = bBold ? FW_BOLD : FW_NORMAL; + if (bItalic > -1) + lFont.lfItalic = bItalic; + if (bUnderline > -1) + lFont.lfUnderline = bUnderline; + + hFont = CreateFontIndirect(&lFont); + return reinterpret_cast(SelectObject(hdc, hFont)); +} + +int GetTextHeight(HDC hdc) { + SIZE size = {0}; + GetTextExtentPoint32(hdc, L"T", 1, &size); + return size.cy; +} + +BOOL GradientRect(HDC hdc, const LPRECT lpRect, DWORD dwColor1, DWORD dwColor2, + bool bVertical) { + TRIVERTEX vertex[2]; + vertex[0].x = lpRect->left; + vertex[0].y = lpRect->top; + vertex[0].Red = GetRValue(dwColor1) << 8; + vertex[0].Green = GetGValue(dwColor1) << 8; + vertex[0].Blue = GetBValue(dwColor1) << 8; + vertex[0].Alpha = 0x0000; + vertex[1].x = lpRect->right; + vertex[1].y = lpRect->bottom; + vertex[1].Red = GetRValue(dwColor2) << 8; + vertex[1].Green = GetGValue(dwColor2) << 8; + vertex[1].Blue = GetBValue(dwColor2) << 8; + vertex[1].Alpha = 0x0000; + + GRADIENT_RECT gRect = {0, 1}; + + return GdiGradientFill(hdc, vertex, 2, &gRect, 1, + static_cast(bVertical)); +} + +BOOL DrawProgressBar(HDC hdc, const LPRECT lpRect, DWORD dwColor1, DWORD dwColor2, + DWORD dwColor3) { + // Draw bottom rect + RECT rect = *lpRect; + rect.top += (rect.bottom - rect.top) / 2; + HBRUSH brush = CreateSolidBrush(dwColor3); + FillRect(hdc, &rect, brush); + DeleteObject(brush); + + // Draw top gradient + rect.bottom = rect.top; + rect.top = lpRect->top; + return GradientRect(hdc, &rect, dwColor1, dwColor2, true); +} + +//////////////////////////////////////////////////////////////////////////////// + +COLORREF HexToARGB(const std::wstring& text) { + int i = text.length() - 6; + if (i < 0) + return 0; + + unsigned int r, g, b; + r = wcstoul(text.substr(i + 0, 2).c_str(), NULL, 16); + g = wcstoul(text.substr(i + 2, 2).c_str(), NULL, 16); + b = wcstoul(text.substr(i + 4, 2).c_str(), NULL, 16); + + return RGB(r, g, b); +} + +win::Rect ResizeRect(const win::Rect& rect_dest, + int src_width, int src_height, + bool stretch, + bool center_x, bool center_y) { + win::Rect rect = rect_dest; + + float dest_width = static_cast(rect_dest.Width()); + float dest_height = static_cast(rect_dest.Height()); + float image_width = static_cast(src_width); + float image_Height = static_cast(src_height); + + // Source < Destination (no need to resize) + if ((image_width < dest_width) && (image_Height < dest_height) && !stretch) { + rect.right = rect.left + src_width; + rect.bottom = rect.top + src_height; + if (center_x) + rect.Offset(static_cast((image_width - image_width) / 2.0f), 0); + if (center_y) + rect.Offset(0, static_cast((dest_height - image_Height) / 2.0f)); + + // Source > Destination (resize) + } else { + // Calculate aspect ratios + float dest_ratio = dest_width / dest_height; + float image_ratio = image_width / image_Height; + + // Width > Height + if (image_ratio > dest_ratio) { + rect.bottom = rect.top + + static_cast(dest_width * (1.0f / image_ratio)); + if (center_y) + rect.Offset(0, static_cast((dest_height - rect.Height()) / 2.0f)); + // Height > Width + } else if (image_ratio < dest_ratio) { + rect.right = rect.left + static_cast(dest_height * image_ratio); + if (center_x) + rect.Offset(static_cast((dest_width - rect.Width()) / 2.0f), 0); + } + } + + return rect; +} + +int ScaleX(int value) { + static int dpi_x = 0; + if (!dpi_x) { + HDC hdc = GetDC(NULL); + if (hdc) { + dpi_x = GetDeviceCaps(hdc, LOGPIXELSX); + ReleaseDC(NULL, hdc); + } + } + + return MulDiv(value, dpi_x, 96); +} + +int ScaleY(int value) { + static int dpi_y = 0; + if (!dpi_y) { + HDC hdc = GetDC(NULL); + if (hdc) { + dpi_y = GetDeviceCaps(hdc, LOGPIXELSY); + ReleaseDC(NULL, hdc); + } + } + + return MulDiv(value, dpi_y, 96); +} + +void RgbToHsv(float r, float g, float b, float& h, float& s, float& v) { + float rgb_min = min(r, min(g, b)); + float rgb_max = max(r, max(g, b)); + float rgb_delta = rgb_max - rgb_min; + + s = rgb_delta / (rgb_max + 1e-20f); + v = rgb_max; + + if (r == rgb_max) { + h = (g - b) / (rgb_delta + 1e-20f); + } else if (g == rgb_max) { + h = 2 + (b - r) / (rgb_delta + 1e-20f); + } else { + h = 4 + (r - g) / (rgb_delta + 1e-20f); + } + + if (h < 0) + h += 6.0f; + h *= (1.0f / 6.0f); +} + +void HsvToRgb(float& r, float& g, float& b, float h, float s, float v) { + if (s == 0) { + r = g = b = v; + return; + } + + h /= 60; + int i = static_cast(floor(h)); + float f = h - i; + float p = v * (1 - s); + float q = v * (1 - s * f); + float t = v * (1 - s * (1 - f)); + + switch (i) { + case 0: + r = v; g = t; b = p; + break; + case 1: + r = q; g = v; b = p; + break; + case 2: + r = p; g = v; b = t; + break; + case 3: + r = p; g = q; b = v; + break; + case 4: + r = t; g = p; b = v; + break; + default: + r = v; g = p; b = q; + break; + } +} + +COLORREF ChangeColorBrightness(COLORREF color, float modifier) { + float r = static_cast(GetRValue(color)) / 255.0f; + float g = static_cast(GetGValue(color)) / 255.0f; + float b = static_cast(GetBValue(color)) / 255.0f; + + float h, s, v; + + RgbToHsv(r, g, b, h, s, v); + + v += modifier; + if (v < 0.0f) v = 0.0f; + if (v > 1.0f) v = 1.0f; + + HsvToRgb(r, g, b, h, s, v); + + return RGB(static_cast(r * 255), + static_cast(g * 255), + static_cast(b * 255)); } \ No newline at end of file diff --git a/gfx.h b/src/base/gfx.h similarity index 67% rename from gfx.h rename to src/base/gfx.h index a32e7324e..c98b21d78 100644 --- a/gfx.h +++ b/src/base/gfx.h @@ -1,52 +1,57 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef GFX_H -#define GFX_H - -#include "std.h" -#include "win32/win_gdi.h" -#include "win32/win_gdiplus.h" - -extern win32::GdiPlus GdiPlus; - -// ============================================================================= - -HFONT ChangeDCFont(HDC hdc, LPCWSTR lpFaceName, INT iSize, BOOL bBold, BOOL bItalic, BOOL bUnderline); -int GetTextHeight(HDC hdc); -BOOL GradientRect(HDC hdc, const LPRECT lpRect, DWORD dwColor1, DWORD dwColor2, bool bVertical); -BOOL DrawProgressBar(HDC hdc, const LPRECT lpRect, DWORD dwColor1, DWORD dwColor2, DWORD dwColor3); - -COLORREF HexToARGB(const wstring& text); -win32::Rect ResizeRect(const win32::Rect& rect_dest, int src_width, int src_height, bool stretch, bool center_x, bool center_y); -int ScaleX(int value); -int ScaleY(int value); -void RgbToHsv(float r, float g, float b, float& h, float& s, float& v); -void HsvToRgb(float& r, float& g, float& b, float h, float s, float v); -COLORREF ChangeColorBrightness(COLORREF color, float modifier); - -class Image { -public: - Image() : data(0) {} - bool Load(const wstring& file); - win32::Dc dc; - win32::Rect rect; - LPARAM data; -}; - -#endif // GFX_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_GFX_H +#define TAIGA_BASE_GFX_H + +#include "win/win_gdi.h" +#include "win/win_gdiplus.h" + +extern class win::GdiPlus GdiPlus; + +namespace base { + +class Image { +public: + Image(); + virtual ~Image() {} + + bool Load(const std::wstring& file); + + win::Dc dc; + win::Rect rect; + LPARAM data; +}; + +} // namespace base + +HFONT ChangeDCFont(HDC hdc, LPCWSTR lpFaceName, INT iSize, BOOL bBold, BOOL bItalic, BOOL bUnderline); +int GetTextHeight(HDC hdc); +BOOL GradientRect(HDC hdc, const LPRECT lpRect, DWORD dwColor1, DWORD dwColor2, bool bVertical); +BOOL DrawProgressBar(HDC hdc, const LPRECT lpRect, DWORD dwColor1, DWORD dwColor2, DWORD dwColor3); + +COLORREF HexToARGB(const std::wstring& text); +win::Rect ResizeRect(const win::Rect& rect_dest, int src_width, int src_height, bool stretch, bool center_x, bool center_y); +int ScaleX(int value); +int ScaleY(int value); +void RgbToHsv(float r, float g, float b, float& h, float& s, float& v); +void HsvToRgb(float& r, float& g, float& b, float h, float s, float v); +COLORREF ChangeColorBrightness(COLORREF color, float modifier); + + +#endif // TAIGA_BASE_GFX_H \ No newline at end of file diff --git a/gzip.cpp b/src/base/gzip.cpp similarity index 76% rename from gzip.cpp rename to src/base/gzip.cpp index a12cc22d3..d17bb8349 100644 --- a/gzip.cpp +++ b/src/base/gzip.cpp @@ -1,74 +1,79 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "third_party/zlib/zlib.h" - -// ============================================================================= - -bool UncompressGzippedFile(const string& file, string& output) { - gzFile gzfile = gzopen(file.c_str(), "rb"); - if (gzfile == NULL) return false; - - char buffer[16384]; - while (true) { - int len = gzread(gzfile, buffer, sizeof(buffer)); - if (len > 0) { - output.append(buffer, len); - } else { - break; - } - } - - gzclose(gzfile); - return true; -} - -bool UncompressGzippedString(const string& input, string& output) { - z_stream stream; - stream.zalloc = Z_NULL; - stream.zfree = Z_NULL; - stream.opaque = NULL; - stream.total_out = 0; - stream.next_in = (BYTE*)&input[0]; - stream.avail_in = input.length(); - - if (inflateInit2(&stream, MAX_WBITS + 32) != Z_OK) { - return false; - } - - size_t buffer_length = input.length() * 2; - char* buffer = (char*)GlobalAlloc(GMEM_ZEROINIT, buffer_length); - size_t total_length = 0; - int status = Z_OK; - - do { - stream.next_out = (BYTE*)buffer; - stream.avail_out = buffer_length; - status = inflate(&stream, Z_SYNC_FLUSH); - if (status == Z_OK || status == Z_STREAM_END) { - output.append(buffer, stream.total_out - total_length); - total_length = stream.total_out; - } - } while (status == Z_OK); - - GlobalFree(buffer); - inflateEnd(&stream); - return status == Z_STREAM_END; +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include + +#include + +#include "gzip.h" + +bool UncompressGzippedFile(const std::string& file, std::string& output) { + gzFile gzfile = gzopen(file.c_str(), "rb"); + + if (gzfile == nullptr) + return false; + + char buffer[16384]; + + while (true) { + int len = gzread(gzfile, buffer, sizeof(buffer)); + if (len > 0) { + output.append(buffer, len); + } else { + break; + } + } + + gzclose(gzfile); + + return true; +} + +bool UncompressGzippedString(const std::string& input, std::string& output) { + z_stream stream; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = NULL; + stream.total_out = 0; + stream.next_in = (BYTE*)&input[0]; + stream.avail_in = input.length(); + + if (inflateInit2(&stream, MAX_WBITS + 32) != Z_OK) + return false; + + size_t buffer_length = input.length() * 2; + char* buffer = (char*)GlobalAlloc(GMEM_ZEROINIT, buffer_length); + + size_t total_length = 0; + int status = Z_OK; + + do { + stream.next_out = (BYTE*)buffer; + stream.avail_out = buffer_length; + status = inflate(&stream, Z_SYNC_FLUSH); + if (status == Z_OK || status == Z_STREAM_END) { + output.append(buffer, stream.total_out - total_length); + total_length = stream.total_out; + } + } while (status == Z_OK); + + GlobalFree(buffer); + inflateEnd(&stream); + + return status == Z_STREAM_END; } \ No newline at end of file diff --git a/src/base/gzip.h b/src/base/gzip.h new file mode 100644 index 000000000..14b77c62c --- /dev/null +++ b/src/base/gzip.h @@ -0,0 +1,27 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_GZIP_H +#define TAIGA_BASE_GZIP_H + +#include + +bool UncompressGzippedFile(const std::string& file, std::string& output); +bool UncompressGzippedString(const std::string& input, std::string& output); + +#endif // TAIGA_BASE_GZIP_H \ No newline at end of file diff --git a/src/base/html.cpp b/src/base/html.cpp new file mode 100644 index 000000000..3699e1f72 --- /dev/null +++ b/src/base/html.cpp @@ -0,0 +1,389 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include +#include + +#include "foreach.h" +#include "html.h" +#include "string.h" + +std::map html_entities; + +void DecodeHtmlEntities(std::wstring& str) { + static size_t min_html_entity_length = 0; + static size_t max_html_entity_length = 0; + + // Build entity map + // Source: http://www.w3.org/TR/html4/sgml/entities.html + if (html_entities.empty()) { + // ISO 8859-1 characters + html_entities[L"nbsp"] = L'\u00A0'; + html_entities[L"iexcl"] = L'\u00A1'; + html_entities[L"cent"] = L'\u00A2'; + html_entities[L"pound"] = L'\u00A3'; + html_entities[L"curren"] = L'\u00A4'; + html_entities[L"yen"] = L'\u00A5'; + html_entities[L"brvbar"] = L'\u00A6'; + html_entities[L"sect"] = L'\u00A7'; + html_entities[L"uml"] = L'\u00A8'; + html_entities[L"copy"] = L'\u00A9'; + html_entities[L"ordf"] = L'\u00AA'; + html_entities[L"laquo"] = L'\u00AB'; + html_entities[L"not"] = L'\u00AC'; + html_entities[L"shy"] = L'\u00AD'; + html_entities[L"reg"] = L'\u00AE'; + html_entities[L"macr"] = L'\u00AF'; + html_entities[L"deg"] = L'\u00B0'; + html_entities[L"plusmn"] = L'\u00B1'; + html_entities[L"sup2"] = L'\u00B2'; + html_entities[L"sup3"] = L'\u00B3'; + html_entities[L"acute"] = L'\u00B4'; + html_entities[L"micro"] = L'\u00B5'; + html_entities[L"para"] = L'\u00B6'; + html_entities[L"middot"] = L'\u00B7'; + html_entities[L"cedil"] = L'\u00B8'; + html_entities[L"sup1"] = L'\u00B9'; + html_entities[L"ordm"] = L'\u00BA'; + html_entities[L"raquo"] = L'\u00BB'; + html_entities[L"frac14"] = L'\u00BC'; + html_entities[L"frac12"] = L'\u00BD'; + html_entities[L"frac34"] = L'\u00BE'; + html_entities[L"iquest"] = L'\u00BF'; + html_entities[L"Agrave"] = L'\u00C0'; + html_entities[L"Aacute"] = L'\u00C1'; + html_entities[L"Acirc"] = L'\u00C2'; + html_entities[L"Atilde"] = L'\u00C3'; + html_entities[L"Auml"] = L'\u00C4'; + html_entities[L"Aring"] = L'\u00C5'; + html_entities[L"AElig"] = L'\u00C6'; + html_entities[L"Ccedil"] = L'\u00C7'; + html_entities[L"Egrave"] = L'\u00C8'; + html_entities[L"Eacute"] = L'\u00C9'; + html_entities[L"Ecirc"] = L'\u00CA'; + html_entities[L"Euml"] = L'\u00CB'; + html_entities[L"Igrave"] = L'\u00CC'; + html_entities[L"Iacute"] = L'\u00CD'; + html_entities[L"Icirc"] = L'\u00CE'; + html_entities[L"Iuml"] = L'\u00CF'; + html_entities[L"ETH"] = L'\u00D0'; + html_entities[L"Ntilde"] = L'\u00D1'; + html_entities[L"Ograve"] = L'\u00D2'; + html_entities[L"Oacute"] = L'\u00D3'; + html_entities[L"Ocirc"] = L'\u00D4'; + html_entities[L"Otilde"] = L'\u00D5'; + html_entities[L"Ouml"] = L'\u00D6'; + html_entities[L"times"] = L'\u00D7'; + html_entities[L"Oslash"] = L'\u00D8'; + html_entities[L"Ugrave"] = L'\u00D9'; + html_entities[L"Uacute"] = L'\u00DA'; + html_entities[L"Ucirc"] = L'\u00DB'; + html_entities[L"Uuml"] = L'\u00DC'; + html_entities[L"Yacute"] = L'\u00DD'; + html_entities[L"THORN"] = L'\u00DE'; + html_entities[L"szlig"] = L'\u00DF'; + html_entities[L"agrave"] = L'\u00E0'; + html_entities[L"aacute"] = L'\u00E1'; + html_entities[L"acirc"] = L'\u00E2'; + html_entities[L"atilde"] = L'\u00E3'; + html_entities[L"auml"] = L'\u00E4'; + html_entities[L"aring"] = L'\u00E5'; + html_entities[L"aelig"] = L'\u00E6'; + html_entities[L"ccedil"] = L'\u00E7'; + html_entities[L"egrave"] = L'\u00E8'; + html_entities[L"eacute"] = L'\u00E9'; + html_entities[L"ecirc"] = L'\u00EA'; + html_entities[L"euml"] = L'\u00EB'; + html_entities[L"igrave"] = L'\u00EC'; + html_entities[L"iacute"] = L'\u00ED'; + html_entities[L"icirc"] = L'\u00EE'; + html_entities[L"iuml"] = L'\u00EF'; + html_entities[L"eth"] = L'\u00F0'; + html_entities[L"ntilde"] = L'\u00F1'; + html_entities[L"ograve"] = L'\u00F2'; + html_entities[L"oacute"] = L'\u00F3'; + html_entities[L"ocirc"] = L'\u00F4'; + html_entities[L"otilde"] = L'\u00F5'; + html_entities[L"ouml"] = L'\u00F6'; + html_entities[L"divide"] = L'\u00F7'; + html_entities[L"oslash"] = L'\u00F8'; + html_entities[L"ugrave"] = L'\u00F9'; + html_entities[L"uacute"] = L'\u00FA'; + html_entities[L"ucirc"] = L'\u00FB'; + html_entities[L"uuml"] = L'\u00FC'; + html_entities[L"yacute"] = L'\u00FD'; + html_entities[L"thorn"] = L'\u00FE'; + html_entities[L"yuml"] = L'\u00FF'; + // Symbols, mathematical symbols, and Greek letters + // Latin Extended-B + html_entities[L"fnof"] = L'\u0192'; + // Greek + html_entities[L"Alpha"] = L'\u0391'; + html_entities[L"Beta"] = L'\u0392'; + html_entities[L"Gamma"] = L'\u0393'; + html_entities[L"Delta"] = L'\u0394'; + html_entities[L"Epsilon"] = L'\u0395'; + html_entities[L"Zeta"] = L'\u0396'; + html_entities[L"Eta"] = L'\u0397'; + html_entities[L"Theta"] = L'\u0398'; + html_entities[L"Iota"] = L'\u0399'; + html_entities[L"Kappa"] = L'\u039A'; + html_entities[L"Lambda"] = L'\u039B'; + html_entities[L"Mu"] = L'\u039C'; + html_entities[L"Nu"] = L'\u039D'; + html_entities[L"Xi"] = L'\u039E'; + html_entities[L"Omicron"] = L'\u039F'; + html_entities[L"Pi"] = L'\u03A0'; + html_entities[L"Rho"] = L'\u03A1'; + html_entities[L"Sigma"] = L'\u03A3'; + html_entities[L"Tau"] = L'\u03A4'; + html_entities[L"Upsilon"] = L'\u03A5'; + html_entities[L"Phi"] = L'\u03A6'; + html_entities[L"Chi"] = L'\u03A7'; + html_entities[L"Psi"] = L'\u03A8'; + html_entities[L"Omega"] = L'\u03A9'; + html_entities[L"alpha"] = L'\u03B1'; + html_entities[L"beta"] = L'\u03B2'; + html_entities[L"gamma"] = L'\u03B3'; + html_entities[L"delta"] = L'\u03B4'; + html_entities[L"epsilon"] = L'\u03B5'; + html_entities[L"zeta"] = L'\u03B6'; + html_entities[L"eta"] = L'\u03B7'; + html_entities[L"theta"] = L'\u03B8'; + html_entities[L"iota"] = L'\u03B9'; + html_entities[L"kappa"] = L'\u03BA'; + html_entities[L"lambda"] = L'\u03BB'; + html_entities[L"mu"] = L'\u03BC'; + html_entities[L"nu"] = L'\u03BD'; + html_entities[L"xi"] = L'\u03BE'; + html_entities[L"omicron"] = L'\u03BF'; + html_entities[L"pi"] = L'\u03C0'; + html_entities[L"rho"] = L'\u03C1'; + html_entities[L"sigmaf"] = L'\u03C2'; + html_entities[L"sigma"] = L'\u03C3'; + html_entities[L"tau"] = L'\u03C4'; + html_entities[L"upsilon"] = L'\u03C5'; + html_entities[L"phi"] = L'\u03C6'; + html_entities[L"chi"] = L'\u03C7'; + html_entities[L"psi"] = L'\u03C8'; + html_entities[L"omega"] = L'\u03C9'; + html_entities[L"thetasym"] = L'\u03D1'; + html_entities[L"upsih"] = L'\u03D2'; + html_entities[L"piv"] = L'\u03D6'; + // General Punctuation + html_entities[L"bull"] = L'\u2022'; + html_entities[L"hellip"] = L'\u2026'; + html_entities[L"prime"] = L'\u2032'; + html_entities[L"Prime"] = L'\u2033'; + html_entities[L"oline"] = L'\u203E'; + html_entities[L"frasl"] = L'\u2044'; + // Letterlike Symbols + html_entities[L"weierp"] = L'\u2118'; + html_entities[L"image"] = L'\u2111'; + html_entities[L"real"] = L'\u211C'; + html_entities[L"trade"] = L'\u2122'; + html_entities[L"alefsym"] = L'\u2135'; + // Arrows + html_entities[L"larr"] = L'\u2190'; + html_entities[L"uarr"] = L'\u2191'; + html_entities[L"rarr"] = L'\u2192'; + html_entities[L"darr"] = L'\u2193'; + html_entities[L"harr"] = L'\u2194'; + html_entities[L"crarr"] = L'\u21B5'; + html_entities[L"lArr"] = L'\u21D0'; + html_entities[L"uArr"] = L'\u21D1'; + html_entities[L"rArr"] = L'\u21D2'; + html_entities[L"dArr"] = L'\u21D3'; + html_entities[L"hArr"] = L'\u21D4'; + // Mathematical Operators + html_entities[L"forall"] = L'\u2200'; + html_entities[L"part"] = L'\u2202'; + html_entities[L"exist"] = L'\u2203'; + html_entities[L"empty"] = L'\u2205'; + html_entities[L"nabla"] = L'\u2207'; + html_entities[L"isin"] = L'\u2208'; + html_entities[L"notin"] = L'\u2209'; + html_entities[L"ni"] = L'\u220B'; + html_entities[L"prod"] = L'\u220F'; + html_entities[L"sum"] = L'\u2211'; + html_entities[L"minus"] = L'\u2212'; + html_entities[L"lowast"] = L'\u2217'; + html_entities[L"radic"] = L'\u221A'; + html_entities[L"prop"] = L'\u221D'; + html_entities[L"infin"] = L'\u221E'; + html_entities[L"ang"] = L'\u2220'; + html_entities[L"and"] = L'\u2227'; + html_entities[L"or"] = L'\u2228'; + html_entities[L"cap"] = L'\u2229'; + html_entities[L"cup"] = L'\u222A'; + html_entities[L"int"] = L'\u222B'; + html_entities[L"there4"] = L'\u2234'; + html_entities[L"sim"] = L'\u223C'; + html_entities[L"cong"] = L'\u2245'; + html_entities[L"asymp"] = L'\u2248'; + html_entities[L"ne"] = L'\u2260'; + html_entities[L"equiv"] = L'\u2261'; + html_entities[L"le"] = L'\u2264'; + html_entities[L"ge"] = L'\u2265'; + html_entities[L"sub"] = L'\u2282'; + html_entities[L"sup"] = L'\u2283'; + html_entities[L"nsub"] = L'\u2284'; + html_entities[L"sube"] = L'\u2286'; + html_entities[L"supe"] = L'\u2287'; + html_entities[L"oplus"] = L'\u2295'; + html_entities[L"otimes"] = L'\u2297'; + html_entities[L"perp"] = L'\u22A5'; + html_entities[L"sdot"] = L'\u22C5'; + // Miscellaneous Technical + html_entities[L"lceil"] = L'\u2308'; + html_entities[L"rceil"] = L'\u2309'; + html_entities[L"lfloor"] = L'\u230A'; + html_entities[L"rfloor"] = L'\u230B'; + html_entities[L"lang"] = L'\u2329'; + html_entities[L"rang"] = L'\u232A'; + // Geometric Shapes + html_entities[L"loz"] = L'\u25CA'; + // Miscellaneous Symbols + html_entities[L"spades"] = L'\u2660'; + html_entities[L"clubs"] = L'\u2663'; + html_entities[L"hearts"] = L'\u2665'; + html_entities[L"diams"] = L'\u2666'; + // Markup-significant and internationalization characters + // C0 Controls and Basic Latin + html_entities[L"quot"] = L'\"'; + html_entities[L"amp"] = L'&'; + html_entities[L"lt"] = L'<'; + html_entities[L"gt"] = L'>'; + // Latin Extended-A + html_entities[L"OElig"] = L'\u0152'; + html_entities[L"oelig"] = L'\u0153'; + html_entities[L"Scaron"] = L'\u0160'; + html_entities[L"scaron"] = L'\u0161'; + html_entities[L"Yuml"] = L'\u0178'; + // Spacing Modifier Letters + html_entities[L"circ"] = L'\u02C6'; + html_entities[L"tilde"] = L'\u02DC'; + // General Punctuation + html_entities[L"ensp"] = L'\u2002'; + html_entities[L"emsp"] = L'\u2003'; + html_entities[L"thinsp"] = L'\u2009'; + html_entities[L"zwnj"] = L'\u200C'; + html_entities[L"zwj"] = L'\u200D'; + html_entities[L"lrm"] = L'\u200E'; + html_entities[L"rlm"] = L'\u200F'; + html_entities[L"ndash"] = L'\u2013'; + html_entities[L"mdash"] = L'\u2014'; + html_entities[L"lsquo"] = L'\u2018'; + html_entities[L"rsquo"] = L'\u2019'; + html_entities[L"sbquo"] = L'\u201A'; + html_entities[L"ldquo"] = L'\u201C'; + html_entities[L"rdquo"] = L'\u201D'; + html_entities[L"bdquo"] = L'\u201E'; + html_entities[L"dagger"] = L'\u2020'; + html_entities[L"Dagger"] = L'\u2021'; + html_entities[L"permil"] = L'\u2030'; + html_entities[L"lsaquo"] = L'\u2039'; + html_entities[L"rsaquo"] = L'\u203A'; + html_entities[L"euro"] = L'\u20AC'; + + foreach_(entity, html_entities) { + if (!min_html_entity_length || + entity->first.length() < min_html_entity_length) + min_html_entity_length = entity->first.length(); + if (entity->first.length() > max_html_entity_length) + max_html_entity_length = entity->first.length(); + } + } + + if (InStr(str, L"&") == -1) + return; + + size_t pos = 0; + size_t reference_pos = 0; + unsigned int character_value = -1; + + for (size_t i = 0; i < str.size(); i++) { + if (str.at(i) == L'&') { + reference_pos = i; + character_value = -1; + if (++i == str.size()) return; + + // Numeric character references + if (str.at(i) == L'#') { + if (++i == str.size()) return; + // Hexadecimal (&#xhhhh;) + if (str.at(i) == L'x') { + if (++i == str.size()) return; + pos = i; + while (i < str.size() && IsHex(str.at(i))) i++; + if (i > pos && i < str.size() && str.at(i) == L';') { + character_value = wcstoul(str.substr(pos, i - pos).c_str(), + nullptr, 16); + } + // Decimal (&#nnnn;) + } else { + pos = i; + while (i < str.size() && IsNumeric(str.at(i))) i++; + if (i > pos && i < str.size() && str.at(i) == L';') { + character_value = ToInt(str.substr(pos, i - pos)); + } + } + + // Character entity references + } else { + pos = i; + while (i < str.size() && IsAlphanumeric(str.at(i))) i++; + if (i > pos && i < str.size() && str.at(i) == L';') { + size_t length = i - pos; + if (length >= min_html_entity_length && + length <= max_html_entity_length) { + std::wstring entity_name = str.substr(pos, length); + if (html_entities.find(entity_name) != html_entities.end()) { + character_value = html_entities[entity_name]; + } + } + } + } + + if (character_value <= 0xFFFD) { + str.replace(reference_pos, i - reference_pos + 1, + std::wstring(1, static_cast(character_value))); + i = reference_pos - 1; + } else { + i = reference_pos; + } + } + } +} + +void StripHtmlTags(std::wstring& str) { + int index_begin = -1; + int index_end = -1; + + do { + index_begin = InStr(str, L"<", 0); + if (index_begin > -1) { + index_end = InStr(str, L">", index_begin); + if (index_end > -1) { + str.erase(index_begin, index_end - index_begin + 1); + } else { + break; + } + } + } while (index_begin > -1); +} \ No newline at end of file diff --git a/src/base/html.h b/src/base/html.h new file mode 100644 index 000000000..74a1fb00e --- /dev/null +++ b/src/base/html.h @@ -0,0 +1,27 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_HTML_H +#define TAIGA_BASE_HTML_H + +#include + +void DecodeHtmlEntities(std::wstring& str); +void StripHtmlTags(std::wstring& str); + +#endif // TAIGA_BASE_HTML_H \ No newline at end of file diff --git a/src/base/http.cpp b/src/base/http.cpp new file mode 100644 index 000000000..5d3961b7d --- /dev/null +++ b/src/base/http.cpp @@ -0,0 +1,192 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "file.h" +#include "http.h" +#include "string.h" + +namespace base { +namespace http { + +Request::Request() + : method(L"GET"), parameter(0) { + // Each HTTP request must have a unique ID, as there are many parts of the + // application that rely on this assumption. + static unsigned int counter = 0; + uid = L"taiga-http-" + + PadChar(ToWstr(static_cast(counter++)), L'0', 10); +} + +Response::Response() + : code(0), parameter(0) { +} + +void Request::Clear() { + method = L"GET"; + url.Clear(); + header.clear(); + body.clear(); +} + +void Response::Clear() { + code = 0; + header.clear(); + body.clear(); +} + +Client::Client() + : allow_reuse_(false), + auto_redirect_(true), + busy_(false), + cancel_(false), + content_encoding_(kContentEncodingNone), + content_length_(0), + current_length_(0), + curl_handle_(nullptr), + header_list_(nullptr), + secure_transaction_(false), + user_agent_(L"Mozilla/5.0") { +} + +Client::~Client() { + Cleanup(false); +} + +//////////////////////////////////////////////////////////////////////////////// + +void Client::Cancel() { + cancel_ = true; +} + +void Client::Cleanup(bool reuse) { + // Close handles + if (curl_handle_) { + if (reuse) { + curl_easy_reset(curl_handle_); + } else { + curl_easy_cleanup(curl_handle_); + curl_handle_ = nullptr; + } + } + if (header_list_) { + curl_slist_free_all(header_list_); + header_list_ = nullptr; + } + if (GetThreadHandle() && + GetThreadId() != GetCurrentThreadId()) { + CloseThreadHandle(); + } + + // Clear request and response + if (!reuse) + request_.Clear(); + response_.Clear(); + + // Clear buffers + optional_data_.clear(); + write_buffer_.clear(); + + // Reset variables + busy_ = false; + cancel_ = false; + content_encoding_ = kContentEncodingNone; + content_length_ = 0; + current_length_ = 0; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool Client::allow_reuse() const { + return allow_reuse_; +} + +bool Client::busy() const { + return busy_; +} + +const Request& Client::request() const { + return request_; +} + +const Response& Client::response() const { + return response_; +} + +curl_off_t Client::content_length() const { + return content_length_; +} + +curl_off_t Client::current_length() const { + return current_length_; +} + +void Client::set_allow_reuse(bool allow) { + allow_reuse_ = allow; +} + +void Client::set_auto_redirect(bool enabled) { + auto_redirect_ = enabled; +} + +void Client::set_download_path(const std::wstring& download_path) { + download_path_ = download_path; + + // Make sure the path is available + if (!download_path.empty()) + CreateFolder(GetPathOnly(download_path)); +} + +void Client::set_proxy(const std::wstring& host, + const std::wstring& username, + const std::wstring& password) { + proxy_host_ = host; + proxy_username_ = username; + proxy_password_ = password; +} + +void Client::set_referer(const std::wstring& referer) { + referer_ = referer; +} + +void Client::set_user_agent(const std::wstring& user_agent) { + user_agent_ = user_agent; +} + +//////////////////////////////////////////////////////////////////////////////// + +CurlGlobal Client::curl_global_; + +CurlGlobal::CurlGlobal() + : initialized_(false) { + if (curl_global_init(CURL_GLOBAL_ALL) == 0) + initialized_ = true; +} + +CurlGlobal::~CurlGlobal() { + if (initialized_) { + curl_global_cleanup(); + initialized_ = false; + } +} + +bool CurlGlobal::initialized() const { + return initialized_; +} + +} // namespace http +} // namespace base \ No newline at end of file diff --git a/src/base/http.h b/src/base/http.h new file mode 100644 index 000000000..3574e8d42 --- /dev/null +++ b/src/base/http.h @@ -0,0 +1,186 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_HTTP_H +#define TAIGA_BASE_HTTP_H + +// Each client will have its own thread +#define TAIGA_HTTP_MULTITHREADED + +#ifdef _DEBUG +#define TAIGA_HTTP_SSL_UNSECURE +#endif + +// CURL definitions +#ifndef CURL_STATICLIB +#define CURL_STATICLIB +#endif +#ifndef HTTP_ONLY +#define HTTP_ONLY +#endif + +#include +#include +#include + +#include + +#include "map.h" +#include "url.h" +#include "win/win_thread.h" + +namespace base { +namespace http { + +typedef base::multimap header_t; + +enum ContentEncoding { + kContentEncodingNone, + kContentEncodingGzip +}; + +//////////////////////////////////////////////////////////////////////////////// + +class Request { +public: + Request(); + virtual ~Request() {} + + void Clear(); + + std::wstring method; + Url url; + + header_t header; + std::wstring body; + + std::wstring uid; + LPARAM parameter; +}; + +class Response { +public: + Response(); + virtual ~Response() {} + + void Clear(); + + unsigned int code; + + header_t header; + std::wstring body; + + std::wstring uid; + LPARAM parameter; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class CurlGlobal { +public: + CurlGlobal(); + ~CurlGlobal(); + + bool initialized() const; + +private: + bool initialized_; +}; + +class Client : public win::Thread { +public: + Client(); + virtual ~Client(); + + void Cancel(); + void Cleanup(bool reuse); + bool MakeRequest(Request request); + + bool allow_reuse() const; + bool busy() const; + const Request& request() const; + const Response& response() const; + curl_off_t content_length() const; + curl_off_t current_length() const; + + void set_allow_reuse(bool allow); + void set_auto_redirect(bool enabled); + void set_download_path(const std::wstring& download_path); + void set_proxy( + const std::wstring& host, + const std::wstring& username, + const std::wstring& password); + void set_referer(const std::wstring& referer); + void set_user_agent(const std::wstring& user_agent); + + virtual void OnError(CURLcode error_code) {} + virtual bool OnHeadersAvailable() { return false; } + virtual bool OnProgress() { return false; } + virtual void OnReadComplete() {} + virtual bool OnRedirect(const std::wstring& address) { return false; } + + DWORD ThreadProc(); + +protected: + Request request_; + Response response_; + + ContentEncoding content_encoding_; + curl_off_t content_length_; + curl_off_t current_length_; + std::string write_buffer_; + + bool allow_reuse_; + bool auto_redirect_; + std::wstring download_path_; + std::wstring proxy_host_; + std::wstring proxy_password_; + std::wstring proxy_username_; + std::wstring referer_; + bool secure_transaction_; + std::wstring user_agent_; + +private: + static size_t HeaderFunction(void*, size_t, size_t, void*); + static size_t WriteFunction(char*, size_t, size_t, void*); + static int DebugCallback(CURL*, curl_infotype, char*, size_t, void*); + static int XferInfoFunction(void*, curl_off_t, curl_off_t, curl_off_t, curl_off_t); + int ProgressFunction(curl_off_t, curl_off_t); + + bool Initialize(); + bool SetRequestOptions(); + bool SendRequest(); + bool Perform(); + + void BuildRequestHeader(); + bool GetResponseHeader(const std::wstring& header); + bool ParseResponseHeader(); + + static CurlGlobal curl_global_; + CURL* curl_handle_; + + bool busy_; + bool cancel_; + curl_slist* header_list_; + std::string optional_data_; +}; + +} // namespace http +} // namespace base + +#endif // TAIGA_BASE_HTTP_H \ No newline at end of file diff --git a/src/base/http_callback.cpp b/src/base/http_callback.cpp new file mode 100644 index 000000000..4f6fe2244 --- /dev/null +++ b/src/base/http_callback.cpp @@ -0,0 +1,135 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "file.h" +#include "http.h" +#include "log.h" +#include "string.h" + +namespace base { +namespace http { + +size_t Client::HeaderFunction(void* ptr, size_t size, size_t nmemb, + void* userdata) { + if (!userdata) + return 0; + + size_t data_size = size * nmemb; + std::string header(static_cast(ptr), data_size); + + auto client = reinterpret_cast(userdata); + + if (header != "\r\n") { + client->GetResponseHeader(StrToWstr(header)); + } else { + if (!client->ParseResponseHeader() || client->OnHeadersAvailable()) + return 0; + } + + return data_size; +} + +size_t Client::WriteFunction(char* ptr, size_t size, size_t nmemb, + void* userdata) { + if (!userdata) + return 0; + + size_t data_size = size * nmemb; + + reinterpret_cast(userdata)->append(ptr, data_size); + + return data_size; +} + +int Client::ProgressFunction(curl_off_t dltotal, curl_off_t dlnow) { + if (cancel_) + return 1; // Abort + + if (response_.header.empty()) + return 0; + if (!dlnow && !dltotal) + return 0; + if (dlnow == current_length_) + return 0; + + current_length_ = dlnow; + + if (OnProgress()) + return 1; // Abort + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// + +int Client::DebugCallback(CURL* curl, curl_infotype infotype, char* data, + size_t size, void* client) { + if (infotype == CURLINFO_DATA_IN || infotype == CURLINFO_DATA_OUT) { + if (client) { + auto client_ = reinterpret_cast(client); + if (client_->content_encoding_ == kContentEncodingGzip) + return 0; + } + } + + std::wstring text; + + switch (infotype) { + case CURLINFO_TEXT: + break; + case CURLINFO_HEADER_IN: + text = L"<= Response header"; + break; + case CURLINFO_HEADER_OUT: + text = L"=> Request header"; + break; + case CURLINFO_DATA_IN: + text = L"<= Recv data"; + break; + case CURLINFO_DATA_OUT: + text = L"=> Send data"; + break; + case CURLINFO_SSL_DATA_IN: + text = L"<= Recv SSL data"; + break; + case CURLINFO_SSL_DATA_OUT: + text = L"=> Send SSL data"; + break; + default: + return 0; + } + + if (!text.empty()) + text += L" | "; + + LOG(LevelDebug, text + StrToWstr(std::string(data, size))); + + return 0; +} + +int Client::XferInfoFunction(void* clientp, + curl_off_t dltotal, curl_off_t dlnow, + curl_off_t ultotal, curl_off_t ulnow) { + if (!clientp) + return 0; + + return reinterpret_cast(clientp)->ProgressFunction(dltotal, dlnow); +} + +} // namespace http +} // namespace base \ No newline at end of file diff --git a/src/base/http_request.cpp b/src/base/http_request.cpp new file mode 100644 index 000000000..841404941 --- /dev/null +++ b/src/base/http_request.cpp @@ -0,0 +1,238 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "file.h" +#include "foreach.h" +#include "http.h" +#include "gzip.h" +#include "log.h" +#include "string.h" +#include "url.h" + +namespace base { +namespace http { + +bool Client::MakeRequest(Request request) { + // Check if the client is busy + if (busy_) { + LOG(LevelWarning, L"Client is busy. ID: " + request_.uid); + return false; + } else { + busy_ = true; + } + + // Set the new request + request_ = request; + LOG(LevelDebug, L"ID: " + request.uid); + + // Set secure transaction state + secure_transaction_ = request.url.protocol == kHttps; + + // Ensure that the response has the same parameter and UID as the request + response_.parameter = request.parameter; + response_.uid = request.uid; + + if (Initialize()) + if (SetRequestOptions()) + if (SendRequest()) + return true; + + Cleanup(false); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool Client::Initialize() { + if (!curl_global_.initialized()) + return false; + + if (!curl_handle_) + curl_handle_ = curl_easy_init(); + + return curl_handle_ != nullptr; +} + +bool Client::SetRequestOptions() { + CURLcode code = CURLE_OK; + + #define TAIGA_CURL_SET_OPTION(option, value) \ + if ((code = curl_easy_setopt(curl_handle_, option, value)) != CURLE_OK) { \ + OnError(code); \ + return false; \ + } + + ////////////////////////////////////////////////////////////////////////////// + // Callback options + +#ifdef _DEBUG + TAIGA_CURL_SET_OPTION(CURLOPT_VERBOSE, TRUE); + TAIGA_CURL_SET_OPTION(CURLOPT_DEBUGFUNCTION, DebugCallback); + TAIGA_CURL_SET_OPTION(CURLOPT_DEBUGDATA, this); +#endif + + TAIGA_CURL_SET_OPTION(CURLOPT_HEADERFUNCTION, HeaderFunction); + TAIGA_CURL_SET_OPTION(CURLOPT_HEADERDATA, this); + + TAIGA_CURL_SET_OPTION(CURLOPT_WRITEFUNCTION, WriteFunction); + TAIGA_CURL_SET_OPTION(CURLOPT_WRITEDATA, &write_buffer_); + + TAIGA_CURL_SET_OPTION(CURLOPT_NOPROGRESS, FALSE); + TAIGA_CURL_SET_OPTION(CURLOPT_XFERINFOFUNCTION, XferInfoFunction); + TAIGA_CURL_SET_OPTION(CURLOPT_XFERINFODATA, this); + + ////////////////////////////////////////////////////////////////////////////// + // Network options + + // Set URL + std::wstring url = request_.url.Build(); + TAIGA_CURL_SET_OPTION(CURLOPT_URL, WstrToStr(url).c_str()); + LOG(LevelDebug, L"URL: " + url); + + // Set protocol + int protocol = secure_transaction_ ? CURLPROTO_HTTPS : CURLPROTO_HTTP; + TAIGA_CURL_SET_OPTION(CURLOPT_PROTOCOLS, protocol); + TAIGA_CURL_SET_OPTION(CURLOPT_REDIR_PROTOCOLS, protocol); + + // Set proxy + if (!proxy_host_.empty()) { + std::string proxy_host = WstrToStr(proxy_host_); + TAIGA_CURL_SET_OPTION(CURLOPT_PROXY, proxy_host.c_str()); + if (!proxy_username_.empty()) { + std::string proxy_username = WstrToStr(proxy_username_); + TAIGA_CURL_SET_OPTION(CURLOPT_PROXYUSERNAME, + proxy_username.c_str()); + } + if (!proxy_password_.empty()) { + std::string proxy_password = WstrToStr(proxy_password_); + TAIGA_CURL_SET_OPTION(CURLOPT_PROXYPASSWORD, + proxy_password.c_str()); + } + } + + ////////////////////////////////////////////////////////////////////////////// + // HTTP options + + // Set auto-redirect + if (auto_redirect_) { + TAIGA_CURL_SET_OPTION(CURLOPT_FOLLOWLOCATION, TRUE); + } + + // Set method + if (request_.method == L"POST") { + optional_data_ = WstrToStr(request_.body); + TAIGA_CURL_SET_OPTION(CURLOPT_POSTFIELDS, optional_data_.c_str()); + TAIGA_CURL_SET_OPTION(CURLOPT_POSTFIELDSIZE, optional_data_.size()); + TAIGA_CURL_SET_OPTION(CURLOPT_POST, TRUE); + } + + // Set referrer + if (!referer_.empty()) { + std::string referer = WstrToStr(referer_); + TAIGA_CURL_SET_OPTION(CURLOPT_REFERER, referer.c_str()); + } + + // Set user agent + if (!user_agent_.empty()) { + std::string user_agent = WstrToStr(user_agent_); + TAIGA_CURL_SET_OPTION(CURLOPT_USERAGENT, user_agent.c_str()); + } + + // Set custom headers + BuildRequestHeader(); + TAIGA_CURL_SET_OPTION(CURLOPT_HTTPHEADER, header_list_); + + ////////////////////////////////////////////////////////////////////////////// + // Security options + +#ifdef TAIGA_HTTP_SSL_UNSECURE + TAIGA_CURL_SET_OPTION(CURLOPT_SSL_VERIFYPEER, 0L); + TAIGA_CURL_SET_OPTION(CURLOPT_SSL_VERIFYHOST, 0L); +#endif + + #undef TAIGA_CURL_SET_OPTION + + return true; +} + +bool Client::SendRequest() { +#ifdef TAIGA_HTTP_MULTITHREADED + return CreateThread(nullptr, 0, 0); +#else + return Perform(); +#endif +} + +bool Client::Perform() { + CURLcode code = curl_easy_perform(curl_handle_); + + if (code == CURLE_OK) { + if (!write_buffer_.empty()) { + if (content_encoding_ == kContentEncodingGzip) { + std::string compressed; + std::swap(write_buffer_, compressed); + UncompressGzippedString(compressed, write_buffer_); + } + response_.body = StrToWstr(write_buffer_); + } + + if (!download_path_.empty()) + SaveToFile((LPCVOID)&write_buffer_.front(), write_buffer_.size(), + download_path_); + + OnReadComplete(); + + } else if (code != CURLE_ABORTED_BY_CALLBACK) { + OnError(code); + } + + Cleanup(allow_reuse_); + + return code == CURLE_OK; +} + +DWORD Client::ThreadProc() { + return Perform(); +} + +//////////////////////////////////////////////////////////////////////////////// + +void Client::BuildRequestHeader() { + // Set acceptable types for the response + if (!request_.header.count(L"Accept")) + request_.header[L"Accept"] = L"*/*"; + + // Set content type for POST and PUT requests + if (request_.method == L"POST" || request_.method == L"PUT") + if (!request_.header.count(L"Content-Type")) + request_.header[L"Content-Type"] = L"application/x-www-form-urlencoded"; + + // Append available header fields + foreach_(it, request_.header) { + std::string header = WstrToStr(it->first); + if (!it->second.empty()) { + header += ": " + WstrToStr(it->second); + } else { + header += ";"; + } + header_list_ = curl_slist_append(header_list_, header.c_str()); + } +} + +} // namespace http +} // namespace base \ No newline at end of file diff --git a/src/base/http_response.cpp b/src/base/http_response.cpp new file mode 100644 index 000000000..7c4457e96 --- /dev/null +++ b/src/base/http_response.cpp @@ -0,0 +1,92 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "foreach.h" +#include "http.h" +#include "string.h" +#include "url.h" + +namespace base { +namespace http { + +bool Client::GetResponseHeader(const std::wstring& header) { + if (header.empty()) + return false; + + std::vector lines; + Split(header, L"\r\n", lines); + + foreach_(line, lines) { + int pos = InStr(*line, L":", 0); + if (pos == -1) { + if (StartsWith(*line, L"HTTP/")) + response_.code = ToInt(InStr(*line, L" ", L" ")); + } else { + std::wstring name = CharLeft(*line, pos); + std::wstring value = line->substr(pos + 2); + // Using insert function instead of operator[] to avoid overwriting + // previous header fields. + response_.header.insert(std::make_pair(name, value)); + } + } + + if (!response_.code) + return false; + + return true; +} + +bool Client::ParseResponseHeader() { + Url location; + + foreach_(it, response_.header) { + std::wstring name = it->first; + std::wstring value = it->second; + + if (IsEqual(name, L"Content-Encoding")) { + if (InStr(value, L"gzip") > -1) { + content_encoding_ = kContentEncodingGzip; + } else { + content_encoding_ = kContentEncodingNone; + } + } else if (IsEqual(name, L"Content-Length")) { + content_length_ = ToInt(value); + } else if (IsEqual(name, L"Location")) { + if (!OnRedirect(value)) { + location.Crack(value); + } else { + return false; + } + } + } + + // Redirection + if (!location.host.empty() && auto_redirect_) { + content_encoding_ = kContentEncodingNone; + content_length_ = 0; + current_length_ = 0; + request_.url.host = location.host; + request_.url.path = location.path; + response_.Clear(); + } + + return true; +} + +} // namespace http +} // namespace base \ No newline at end of file diff --git a/src/base/json.cpp b/src/base/json.cpp new file mode 100644 index 000000000..e7070bdbe --- /dev/null +++ b/src/base/json.cpp @@ -0,0 +1,32 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "json.h" +#include "string.h" + +bool JsonReadArray(const Json::Value& root, const std::string& name, + std::vector& output) { + size_t previous_size = output.size(); + + auto& value = root[name.c_str()]; + + for (size_t i = 0; i < value.size(); i++) + output.push_back(StrToWstr(value[i].asString())); + + return output.size() > previous_size; +} \ No newline at end of file diff --git a/src/base/json.h b/src/base/json.h new file mode 100644 index 000000000..d44fbf995 --- /dev/null +++ b/src/base/json.h @@ -0,0 +1,29 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_JSON_H +#define TAIGA_BASE_JSON_H + +#include +#include + +#include + +bool JsonReadArray(const Json::Value& root, const std::string& name, std::vector& output); + +#endif // TAIGA_BASE_JSON_H \ No newline at end of file diff --git a/src/base/log.cpp b/src/base/log.cpp new file mode 100644 index 000000000..5d1924756 --- /dev/null +++ b/src/base/log.cpp @@ -0,0 +1,124 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include +#include + +#include "foreach.h" +#include "log.h" +#include "string.h" +#include "time.h" + +const char* SeverityLevels[] = { + "Emergency", + "Alert", + "Critical", + "Error", + "Warning", + "Notice", + "Informational", + "Debug" +}; + +class Logger Logger; + +Logger::Logger() + : severity_level_(LevelDebug) { +} + +void Logger::Log(int severity_level, const std::wstring& file, int line, + const std::wstring& function, std::wstring text) { + win::Lock lock(critical_section_); + + if (severity_level <= severity_level_) { + Trim(text, L" \r\n"); + + std::string output_text; + + output_text += WstrToStr(std::wstring(GetDate()) + L" " + GetTime() + L" "); + output_text += "[" + std::string(SeverityLevels[severity_level]) + "] "; + output_text += WstrToStr(GetFileName(file) + L":" + ToWstr(line) + L" " + function + L" | "); + + std::string padding(output_text.length(), ' '); + std::vector lines; + Split(text, L"\r\n", lines); + foreach_(it, lines) { + Trim(*it); + if (!it->empty()) { + if (it != lines.begin()) + output_text += padding; + output_text += WstrToStr(*it + L"\r\n"); + } + } + +#ifdef _DEBUG + OutputDebugStringA(output_text.c_str()); +#endif + + if (!output_path_.empty()) { + std::ofstream stream; + stream.open(output_path_, std::ofstream::app | + std::ios::binary | + std::ofstream::out); + if (stream.is_open()) { + stream.write(output_text.c_str(), output_text.size()); + stream.close(); + } + } + } +} + +void Logger::SetOutputPath(const std::wstring& path) { + output_path_ = path; +} + +void Logger::SetSeverityLevel(int severity_level) { + severity_level_ = severity_level; +} + +//////////////////////////////////////////////////////////////////////////////// + +std::wstring Logger::FormatError(DWORD error, LPCWSTR source) { + DWORD flags = FORMAT_MESSAGE_IGNORE_INSERTS; + HMODULE module_handle = nullptr; + + const DWORD size = 101; + WCHAR buffer[size]; + + if (source) { + flags |= FORMAT_MESSAGE_FROM_HMODULE; + module_handle = LoadLibrary(source); + if (!module_handle) + return std::wstring(); + } else { + flags |= FORMAT_MESSAGE_FROM_SYSTEM; + } + + DWORD language_id = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT); + + if (FormatMessage(flags, module_handle, error, language_id, + buffer, size, nullptr)) { + if (module_handle) + FreeLibrary(module_handle); + return buffer; + } else { + if (module_handle) + FreeLibrary(module_handle); + return ToWstr(error); + } +} \ No newline at end of file diff --git a/logger.h b/src/base/log.h similarity index 68% rename from logger.h rename to src/base/log.h index 9c38631e5..ff77b36e8 100644 --- a/logger.h +++ b/src/base/log.h @@ -1,59 +1,62 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef LOGGER_H -#define LOGGER_H - -#include "std.h" -#include "win32/win_thread.h" - -enum SeverityLevels { - LevelEmergency, - LevelAlert, - LevelCritical, - LevelError, - LevelWarning, - LevelNotice, - LevelInformational, - LevelDebug -}; - -class Logger { -public: - Logger(); - virtual ~Logger() {} - - void Log(int severity_level, const wstring& file, int line, - const wstring& function, const wstring& text); - void SetOutputPath(const wstring& path); - void SetSeverityLevel(int severity_level); - -private: - win32::CriticalSection critical_section_; - wstring output_path_; - int severity_level_; -}; - -extern class Logger Logger; - -#ifndef LOG -#define LOG(level, text) \ - Logger.Log(level, __FILEW__, __LINE__, __FUNCTIONW__, text) -#endif - -#endif // LOGGER_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_LOG_H +#define TAIGA_BASE_LOG_H + +#include + +#include "win/win_thread.h" + +enum SeverityLevels { + LevelEmergency, + LevelAlert, + LevelCritical, + LevelError, + LevelWarning, + LevelNotice, + LevelInformational, + LevelDebug +}; + +class Logger { +public: + Logger(); + virtual ~Logger() {} + + void Log(int severity_level, const std::wstring& file, int line, + const std::wstring& function, std::wstring text); + void SetOutputPath(const std::wstring& path); + void SetSeverityLevel(int severity_level); + + static std::wstring FormatError(DWORD error, LPCWSTR source = nullptr); + +private: + win::CriticalSection critical_section_; + std::wstring output_path_; + int severity_level_; +}; + +extern class Logger Logger; + +#ifndef LOG +#define LOG(level, text) \ + Logger.Log(level, __FILEW__, __LINE__, __FUNCTIONW__, text) +#endif + +#endif // TAIGA_BASE_LOG_H \ No newline at end of file diff --git a/src/base/map.h b/src/base/map.h new file mode 100644 index 000000000..9d4c717de --- /dev/null +++ b/src/base/map.h @@ -0,0 +1,42 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_MAP_H +#define TAIGA_BASE_MAP_H + +#include + +namespace base { + +// A multimap implementation that allows accessing the first value that is +// mapped to a given key. + +template +class multimap : public std::multimap { +public: + mapped_type& operator[] (const key_type& key) { + auto it = this->find(key); + if (it == this->end()) + it = this->insert(std::pair(key, mapped_type())); + return it->second; + } +}; + +} // namespace base + +#endif // TAIGA_BASE_MAP_H \ No newline at end of file diff --git a/src/base/oauth.cpp b/src/base/oauth.cpp new file mode 100644 index 000000000..a1233bc1e --- /dev/null +++ b/src/base/oauth.cpp @@ -0,0 +1,199 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include +#include +#include + +#include "base64.h" +#include "foreach.h" +#include "crypto.h" +#include "string.h" +#include "oauth.h" +#include "url.h" + +#ifndef SIZEOF +#define SIZEOF(x) (sizeof(x) / sizeof(*x)) +#endif + +//////////////////////////////////////////////////////////////////////////////// +// OAuth implementation is based on codebrook-twitter-oauth example code +// Copyright (c) 2010 Brook Miles +// https://code.google.com/p/codebrook-twitter-oauth/ + +std::wstring OAuth::BuildAuthorizationHeader( + const std::wstring& url, + const std::wstring& http_method, + const oauth_parameter_t* post_parameters, + const std::wstring& oauth_token, + const std::wstring& oauth_token_secret, + const std::wstring& pin) { + // Build request parameters + oauth_parameter_t get_parameters = ParseQuery(Url(url).query); + + // Build signed OAuth parameters + oauth_parameter_t signed_parameters = + BuildSignedParameters(get_parameters, url, http_method, post_parameters, + oauth_token, oauth_token_secret, pin); + + // Build and return OAuth header + std::wstring oauth_header = L"OAuth "; + foreach_c_(it, signed_parameters) { + if (it != signed_parameters.begin()) + oauth_header += L", "; + oauth_header += it->first + L"=\"" + it->second + L"\""; + } + return oauth_header; +} + +oauth_parameter_t OAuth::ParseQueryString(const std::wstring& url) { + oauth_parameter_t parsed_parameters; + + std::vector parameters; + Split(url, L"&", parameters); + + foreach_(parameter, parameters) { + std::vector elements; + Split(*parameter, L"=", elements); + if (elements.size() == 2) { + parsed_parameters[elements[0]] = elements[1]; + } + } + + return parsed_parameters; +} + +//////////////////////////////////////////////////////////////////////////////// + +oauth_parameter_t OAuth::BuildSignedParameters( + const oauth_parameter_t& get_parameters, + const std::wstring& url, + const std::wstring& http_method, + const oauth_parameter_t* post_parameters, + const std::wstring& oauth_token, + const std::wstring& oauth_token_secret, + const std::wstring& pin) { + // Create OAuth parameters + oauth_parameter_t oauth_parameters; + oauth_parameters[L"oauth_callback"] = L"oob"; + oauth_parameters[L"oauth_consumer_key"] = consumer_key; + oauth_parameters[L"oauth_nonce"] = CreateNonce(); + oauth_parameters[L"oauth_signature_method"] = L"HMAC-SHA1"; + oauth_parameters[L"oauth_timestamp"] = CreateTimestamp(); + oauth_parameters[L"oauth_version"] = L"1.0"; + + // Add the request token + if (!oauth_token.empty()) + oauth_parameters[L"oauth_token"] = oauth_token; + // Add the authorization PIN + if (!pin.empty()) + oauth_parameters[L"oauth_verifier"] = pin; + + // Create a parameter list containing both OAuth and original parameters + oauth_parameter_t all_parameters = get_parameters; + if (IsEqual(http_method, L"POST") && post_parameters) + all_parameters.insert(post_parameters->begin(), post_parameters->end()); + all_parameters.insert(oauth_parameters.begin(), oauth_parameters.end()); + + // Prepare the signature base + std::wstring normal_url = NormalizeUrl(url); + std::wstring sorted_parameters = SortParameters(all_parameters); + std::wstring signature_base = http_method + + L"&" + EncodeUrl(normal_url) + + L"&" + EncodeUrl(sorted_parameters); + + // Obtain a signature and add it to header parameters + std::wstring signature = CreateSignature(signature_base, oauth_token_secret); + oauth_parameters[L"oauth_signature"] = signature; + + return oauth_parameters; +} + +//////////////////////////////////////////////////////////////////////////////// + +std::wstring OAuth::CreateNonce() { + static const wchar_t alphanumeric[] = + L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + std::wstring nonce; + + for (int i = 0; i <= 16; ++i) + nonce += alphanumeric[rand() % (SIZEOF(alphanumeric) - 1)]; + + return nonce; +} + +std::wstring OAuth::CreateSignature(const std::wstring& signature_base, + const std::wstring& oauth_token_secret) { + // Create a SHA-1 hash of signature + std::wstring key = EncodeUrl(consumer_secret) + + L"&" + EncodeUrl(oauth_token_secret); + std::string hash = HmacSha1(WstrToStr(key), WstrToStr(signature_base)); + + // Encode signature in Base64 + std::wstring signature = Base64Encode(hash); + + // Return URL-encoded signature + return EncodeUrl(signature); +} + +std::wstring OAuth::CreateTimestamp() { + __time64_t utcNow; + __time64_t ret = _time64(&utcNow); + wchar_t buf[100] = {'\0'}; + swprintf_s(buf, SIZEOF(buf), L"%I64u", utcNow); + return buf; +} + +std::wstring OAuth::NormalizeUrl(const std::wstring& url) { + Url url_components = url; + + // Strip off ? and # elements in the path + url_components.query.clear(); + url_components.fragment.clear(); + + // Build normal URL + std::wstring normal_url = url_components.Build(); + + return normal_url; +} + +oauth_parameter_t OAuth::ParseQuery(const query_t& query) { + oauth_parameter_t parameters; + + foreach_(it, query) + parameters[it->first] = it->second; + + return parameters; +} + +std::wstring OAuth::SortParameters(const oauth_parameter_t& parameters) { + std::list sorted; + foreach_c_(it, parameters) { + std::wstring param = it->first + L"=" + it->second; + sorted.push_back(param); + } + sorted.sort(); + + std::wstring params; + foreach_(it, sorted) { + if (params.size() > 0) + params += L"&"; + params += *it; + } + return params; +} \ No newline at end of file diff --git a/src/base/oauth.h b/src/base/oauth.h new file mode 100644 index 000000000..e1de12efe --- /dev/null +++ b/src/base/oauth.h @@ -0,0 +1,65 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_THIRD_PARTY_OAUTH_H +#define TAIGA_THIRD_PARTY_OAUTH_H + +#include +#include + +#include "url.h" + +typedef std::map oauth_parameter_t; + +class OAuth { +public: + OAuth() {} + ~OAuth() {} + + std::wstring BuildAuthorizationHeader( + const std::wstring& url, + const std::wstring& http_method, + const oauth_parameter_t* post_parameters = nullptr, + const std::wstring& oauth_token = L"", + const std::wstring& oauth_token_secret = L"", + const std::wstring& pin = L""); + oauth_parameter_t ParseQueryString(const std::wstring& url); + + std::wstring consumer_key; + std::wstring consumer_secret; + +private: + oauth_parameter_t BuildSignedParameters( + const oauth_parameter_t& get_parameters, + const std::wstring& url, + const std::wstring& http_method, + const oauth_parameter_t* post_parameters, + const std::wstring& oauth_token, + const std::wstring& oauth_token_secret, + const std::wstring& pin); + + std::wstring CreateNonce(); + std::wstring CreateSignature(const std::wstring& signature_base, const std::wstring& oauth_token_secret); + std::wstring CreateTimestamp(); + + std::wstring NormalizeUrl(const std::wstring& url); + oauth_parameter_t ParseQuery(const query_t& query); + std::wstring SortParameters(const oauth_parameter_t& parameters); +}; + +#endif // TAIGA_THIRD_PARTY_OAUTH_H \ No newline at end of file diff --git a/optional.h b/src/base/optional.h similarity index 82% rename from optional.h rename to src/base/optional.h index 47926a8b1..e3702c834 100644 --- a/optional.h +++ b/src/base/optional.h @@ -1,54 +1,52 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef OPTIONAL_H -#define OPTIONAL_H - -#include "std.h" - -template -class Optional { - public: - Optional() : initialized_(false) {} - Optional(const T& value) : initialized_(true), value_(value) {} - virtual ~Optional() {} - - void Reset() { - initialized_ = false; - } - - operator bool() const { - return initialized_; - } - - const T& operator *() const { - return value_; - } - - T& operator =(const T& value) { - value_ = value; - initialized_ = true; - return value_; - } - - private: - bool initialized_; - T value_; -}; - -#endif // OPTIONAL_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_OPTIONAL_H +#define TAIGA_BASE_OPTIONAL_H + +template +class Optional { +public: + Optional() : initialized_(false) {} + Optional(const T& value) : initialized_(true), value_(value) {} + virtual ~Optional() {} + + void Reset() { + initialized_ = false; + } + + operator bool() const { + return initialized_; + } + + const T& operator *() const { + return value_; + } + + T& operator =(const T& value) { + value_ = value; + initialized_ = true; + return value_; + } + +private: + bool initialized_; + T value_; +}; + +#endif // TAIGA_BASE_OPTIONAL_H \ No newline at end of file diff --git a/process.cpp b/src/base/process.cpp similarity index 68% rename from process.cpp rename to src/base/process.cpp index 342ab6be1..72d1dd80a 100644 --- a/process.cpp +++ b/src/base/process.cpp @@ -1,374 +1,403 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" -#include "logger.h" -#include "string.h" -#include "win32/win_main.h" - -#define NT_SUCCESS(x) ((x) >= 0) -#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004 - -#define ObjectBasicInformation 0 -#define ObjectNameInformation 1 -#define ObjectTypeInformation 2 -#define SystemHandleInformation 16 -#define SystemHandleInformationEx 64 - -typedef NTSTATUS (NTAPI *_NtQuerySystemInformation)( - ULONG SystemInformationClass, - PVOID SystemInformation, - ULONG SystemInformationLength, - PULONG ReturnLength -); -typedef NTSTATUS (NTAPI *_NtDuplicateObject)( - HANDLE SourceProcessHandle, - HANDLE SourceHandle, - HANDLE TargetProcessHandle, - PHANDLE TargetHandle, - ACCESS_MASK DesiredAccess, - ULONG Attributes, - ULONG Options -); -typedef NTSTATUS (NTAPI *_NtQueryObject)( - HANDLE ObjectHandle, - ULONG ObjectInformationClass, - PVOID ObjectInformation, - ULONG ObjectInformationLength, - PULONG ReturnLength -); - -typedef struct _UNICODE_STRING { - USHORT Length; - USHORT MaximumLength; - PWSTR Buffer; -} UNICODE_STRING, *PUNICODE_STRING; - -typedef struct _SYSTEM_HANDLE { - ULONG ProcessId; - BYTE ObjectTypeNumber; - BYTE Flags; - USHORT Handle; - PVOID Object; - ACCESS_MASK GrantedAccess; -} SYSTEM_HANDLE, *PSYSTEM_HANDLE; -typedef struct _SYSTEM_HANDLE_INFORMATION { - ULONG HandleCount; - SYSTEM_HANDLE Handles[1]; -} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION; - -typedef struct _SYSTEM_HANDLE_EX { - PVOID Object; - HANDLE ProcessId; - HANDLE Handle; - ULONG GrantedAccess; - USHORT CreatorBackTraceIndex; - USHORT ObjectTypeIndex; - ULONG HandleAttributes; - ULONG Reserved; -} SYSTEM_HANDLE_EX, *PSYSTEM_HANDLE_EX; -typedef struct _SYSTEM_HANDLE_INFORMATION_EX { - ULONG_PTR HandleCount; - ULONG_PTR Reserved; - SYSTEM_HANDLE_EX Handles[1]; -} SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX; - -typedef enum _POOL_TYPE { - NonPagedPool, - PagedPool, - NonPagedPoolMustSucceed, - DontUseThisType, - NonPagedPoolCacheAligned, - PagedPoolCacheAligned, - NonPagedPoolCacheAlignedMustS -} POOL_TYPE, *PPOOL_TYPE; - -typedef struct _OBJECT_TYPE_INFORMATION { - UNICODE_STRING Name; - ULONG TotalNumberOfObjects; - ULONG TotalNumberOfHandles; - ULONG TotalPagedPoolUsage; - ULONG TotalNonPagedPoolUsage; - ULONG TotalNamePoolUsage; - ULONG TotalHandleTableUsage; - ULONG HighWaterNumberOfObjects; - ULONG HighWaterNumberOfHandles; - ULONG HighWaterPagedPoolUsage; - ULONG HighWaterNonPagedPoolUsage; - ULONG HighWaterNamePoolUsage; - ULONG HighWaterHandleTableUsage; - ULONG InvalidAttributes; - GENERIC_MAPPING GenericMapping; - ULONG ValidAccess; - BOOLEAN SecurityRequired; - BOOLEAN MaintainHandleCount; - USHORT MaintainTypeList; - POOL_TYPE PoolType; - ULONG PagedPoolUsage; - ULONG NonPagedPoolUsage; -} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; - -// ============================================================================= - -PVOID GetLibraryProcAddress(PSTR LibraryName, PSTR ProcName) { - return GetProcAddress(GetModuleHandleA(LibraryName), ProcName); -} - -BOOL GetProcessFiles(ULONG process_id, vector& files_vector) { - _NtQuerySystemInformation NtQuerySystemInformation = reinterpret_cast<_NtQuerySystemInformation> - (GetLibraryProcAddress("ntdll.dll", "NtQuerySystemInformation")); - _NtDuplicateObject NtDuplicateObject = reinterpret_cast<_NtDuplicateObject> - (GetLibraryProcAddress("ntdll.dll", "NtDuplicateObject")); - _NtQueryObject NtQueryObject = reinterpret_cast<_NtQueryObject> - (GetLibraryProcAddress("ntdll.dll", "NtQueryObject")); - - HANDLE processHandle; - if (!(processHandle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, process_id))) { - return FALSE; - } - - NTSTATUS status; - ULONG handleInfoSize = 0x10000; - PSYSTEM_HANDLE_INFORMATION_EX handleInfo; - handleInfo = reinterpret_cast(malloc(handleInfoSize)); - while ((status = NtQuerySystemInformation(SystemHandleInformationEx, handleInfo, handleInfoSize, NULL)) == STATUS_INFO_LENGTH_MISMATCH) { - handleInfo = reinterpret_cast(realloc(handleInfo, handleInfoSize *= 2)); - if (handleInfoSize > 16 * 1024 * 1024) return FALSE; - } - if (!NT_SUCCESS(status)) return FALSE; - - // Type index for files varies between OS versions - static unsigned short objectTypeFile = 0; - /* - switch (win32::GetWinVersion()) { - case win32::VERSION_VISTA: - objectTypeFile = 25; - break; - case win32::VERSION_WIN8: - objectTypeFile = 31; - break; - default: - objectTypeFile = 28; - break; - } - */ - - for (ULONG_PTR i = 0; i < handleInfo->HandleCount; i++) { - SYSTEM_HANDLE_EX handle = handleInfo->Handles[i]; - HANDLE dupHandle = NULL; - POBJECT_TYPE_INFORMATION objectTypeInfo; - PVOID objectNameInfo; - UNICODE_STRING objectName; - ULONG returnLength; - - // Check if this handle belongs to the PID the user specified - if (reinterpret_cast(handle.ProcessId) != process_id) { - continue; - } - // Skip if the handle does not belong to a file - if (objectTypeFile > 0 && handle.ObjectTypeIndex != objectTypeFile) { - continue; - } - // Skip access codes which can cause NtDuplicateObject() or NtQueryObject() to hang - if (handle.GrantedAccess == 0x00100000 || handle.GrantedAccess == 0x00120189 || - handle.GrantedAccess == 0x0012019f || handle.GrantedAccess == 0x001a019f) { - continue; - } - - // Duplicate the handle so we can query it - if (!NT_SUCCESS(NtDuplicateObject(processHandle, handle.Handle, GetCurrentProcess(), &dupHandle, 0, 0, 0))) { - continue; - } - // Query the object type - objectTypeInfo = reinterpret_cast(malloc(0x1000)); - if (!NT_SUCCESS(NtQueryObject(dupHandle, ObjectTypeInformation, objectTypeInfo, 0x1000, NULL))) { - CloseHandle(dupHandle); - continue; - } - // Determine the type index for files - if (objectTypeFile == 0) { - wstring type_name(objectTypeInfo->Name.Buffer, objectTypeInfo->Name.Length / 2); - if (IsEqual(type_name, L"File")) { - objectTypeFile = handle.ObjectTypeIndex; - LOG(LevelDebug, L"objectTypeFile is set to " + ToWstr(objectTypeFile) + L"."); - } else { - continue; - } - } - - objectNameInfo = malloc(0x1000); - - UINT errorMode = SetErrorMode(SEM_FAILCRITICALERRORS); - SetErrorMode(errorMode | SEM_FAILCRITICALERRORS); - - if (!NT_SUCCESS(NtQueryObject(dupHandle, ObjectNameInformation, objectNameInfo, 0x1000, &returnLength))) { - if (returnLength > 0x10000) { - free(objectTypeInfo); - free(objectNameInfo); - CloseHandle(dupHandle); - SetErrorMode(errorMode); - continue; - } - // Reallocate the buffer and try again - objectNameInfo = realloc(objectNameInfo, returnLength); - if (!NT_SUCCESS(NtQueryObject(dupHandle, ObjectNameInformation, objectNameInfo, returnLength, NULL))) { - free(objectTypeInfo); - free(objectNameInfo); - CloseHandle(dupHandle); - SetErrorMode(errorMode); - continue; - } - } - - DWORD errorCode = GetLastError(); - if (errorCode != ERROR_SUCCESS) { - SetLastError(ERROR_SUCCESS); - } - SetErrorMode(errorMode); - - // Cast our buffer into a UNICODE_STRING - objectName = *reinterpret_cast(objectNameInfo); - // Add file path to our list - if (objectName.Length) { - wstring object_name(objectName.Buffer, objectName.Length / 2); - files_vector.push_back(object_name); - } - - free(objectTypeInfo); - free(objectNameInfo); - CloseHandle(dupHandle); - } - - free(handleInfo); - CloseHandle(processHandle); - return TRUE; -} - -// ============================================================================= - -void ActivateWindow(HWND hwnd) { - if (IsIconic(hwnd)) SendMessage(hwnd, WM_SYSCOMMAND, SC_RESTORE, NULL); - if (!IsWindowVisible(hwnd)) ShowWindow(hwnd, SW_SHOWNORMAL); - SetForegroundWindow(hwnd); - BringWindowToTop(hwnd); -} - -bool CheckInstance(LPCWSTR mutex_name, LPCWSTR class_name) { - if (CreateMutex(NULL, FALSE, mutex_name) == NULL || - GetLastError() == ERROR_ALREADY_EXISTS || - GetLastError() == ERROR_ACCESS_DENIED) { - HWND hwnd = FindWindow(class_name, NULL); - if (IsWindow(hwnd)) { - ActivateWindow(hwnd); - FlashWindow(hwnd, TRUE); - } - return TRUE; - } - return FALSE; -} - -wstring GetWindowClass(HWND hwnd) { - WCHAR buff[MAX_PATH]; - GetClassName(hwnd, buff, MAX_PATH); - return buff; -} - -wstring GetWindowTitle(HWND hwnd) { - WCHAR buff[MAX_PATH]; - GetWindowText(hwnd, buff, MAX_PATH); - return buff; -} - -wstring GetWindowPath(HWND hwnd) { - DWORD dwProcessId; - DWORD dwSize = MAX_PATH; - WCHAR buff[MAX_PATH] = {'\0'}; - - GetWindowThreadProcessId(hwnd, &dwProcessId); - HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcessId); - - typedef DWORD (WINAPI *_QueryFullProcessImageName)( - HANDLE hProcess, DWORD dwFlags, LPTSTR lpExeName, PDWORD lpdwSize); - - if (hProcess != NULL) { - bool success = false; - if (win32::GetWinVersion() >= win32::VERSION_VISTA) { - HMODULE hKernel32 = LoadLibrary(L"kernel32.dll"); - if (hKernel32 != NULL) { - _QueryFullProcessImageName proc = - (_QueryFullProcessImageName)GetProcAddress(hKernel32, "QueryFullProcessImageNameW"); - if (proc != NULL) { - success = (proc)(hProcess, 0, buff, &dwSize) != 0; - } - FreeLibrary(hKernel32); - } - } - if (!success) { - GetModuleFileNameEx(hProcess, NULL, buff, dwSize); - } - CloseHandle(hProcess); - } - - return buff; -} - -bool IsFullscreen(HWND hwnd) { - LONG style = GetWindowLong(hwnd, GWL_EXSTYLE); - - if (style & WS_EX_TOPMOST) { - RECT rect; GetClientRect(hwnd, &rect); - if (rect.right >= GetSystemMetrics(SM_CXSCREEN) && - rect.bottom >= GetSystemMetrics(SM_CYSCREEN)) { - return true; - } - } - - return false; -} - -bool TranslateDeviceName(wstring& path) { - size_t pos = path.find('\\', 8); - if (pos == wstring::npos) return false; - wstring device_name = path.substr(0, pos); - path = path.substr(pos); - - const int size = 1024; - WCHAR szTemp[size] = {'\0'}; - if (!GetLogicalDriveStrings(size - 1, szTemp)) { - return false; - } - - WCHAR* p = szTemp; - bool bFound = false; - WCHAR szName[MAX_PATH]; - WCHAR szDrive[3] = L" :"; - - do { - *szDrive = *p; - if (QueryDosDevice(szDrive, szName, MAX_PATH)) { - if (device_name == szName) { - device_name = szDrive; - bFound = true; - } - } - while (*p++); - } while (!bFound && *p); - - path = device_name + path; - return bFound; +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include +#include + +#include "log.h" +#include "process.h" +#include "string.h" + +#define NT_SUCCESS(x) ((x) >= 0) +#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004 + +#define ObjectBasicInformation 0 +#define ObjectNameInformation 1 +#define ObjectTypeInformation 2 +#define SystemHandleInformation 16 +#define SystemHandleInformationEx 64 + +typedef NTSTATUS (NTAPI *_NtQuerySystemInformation)( + ULONG SystemInformationClass, + PVOID SystemInformation, + ULONG SystemInformationLength, + PULONG ReturnLength +); +typedef NTSTATUS (NTAPI *_NtDuplicateObject)( + HANDLE SourceProcessHandle, + HANDLE SourceHandle, + HANDLE TargetProcessHandle, + PHANDLE TargetHandle, + ACCESS_MASK DesiredAccess, + ULONG Attributes, + ULONG Options +); +typedef NTSTATUS (NTAPI *_NtQueryObject)( + HANDLE ObjectHandle, + ULONG ObjectInformationClass, + PVOID ObjectInformation, + ULONG ObjectInformationLength, + PULONG ReturnLength +); + +typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} UNICODE_STRING, *PUNICODE_STRING; + +typedef struct _SYSTEM_HANDLE { + ULONG ProcessId; + BYTE ObjectTypeNumber; + BYTE Flags; + USHORT Handle; + PVOID Object; + ACCESS_MASK GrantedAccess; +} SYSTEM_HANDLE, *PSYSTEM_HANDLE; +typedef struct _SYSTEM_HANDLE_INFORMATION { + ULONG HandleCount; + SYSTEM_HANDLE Handles[1]; +} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION; + +typedef struct _SYSTEM_HANDLE_EX { + PVOID Object; + HANDLE ProcessId; + HANDLE Handle; + ULONG GrantedAccess; + USHORT CreatorBackTraceIndex; + USHORT ObjectTypeIndex; + ULONG HandleAttributes; + ULONG Reserved; +} SYSTEM_HANDLE_EX, *PSYSTEM_HANDLE_EX; +typedef struct _SYSTEM_HANDLE_INFORMATION_EX { + ULONG_PTR HandleCount; + ULONG_PTR Reserved; + SYSTEM_HANDLE_EX Handles[1]; +} SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX; + +typedef enum _POOL_TYPE { + NonPagedPool, + PagedPool, + NonPagedPoolMustSucceed, + DontUseThisType, + NonPagedPoolCacheAligned, + PagedPoolCacheAligned, + NonPagedPoolCacheAlignedMustS +} POOL_TYPE, *PPOOL_TYPE; + +typedef struct _OBJECT_TYPE_INFORMATION { + UNICODE_STRING Name; + ULONG TotalNumberOfObjects; + ULONG TotalNumberOfHandles; + ULONG TotalPagedPoolUsage; + ULONG TotalNonPagedPoolUsage; + ULONG TotalNamePoolUsage; + ULONG TotalHandleTableUsage; + ULONG HighWaterNumberOfObjects; + ULONG HighWaterNumberOfHandles; + ULONG HighWaterPagedPoolUsage; + ULONG HighWaterNonPagedPoolUsage; + ULONG HighWaterNamePoolUsage; + ULONG HighWaterHandleTableUsage; + ULONG InvalidAttributes; + GENERIC_MAPPING GenericMapping; + ULONG ValidAccess; + BOOLEAN SecurityRequired; + BOOLEAN MaintainHandleCount; + USHORT MaintainTypeList; + POOL_TYPE PoolType; + ULONG PagedPoolUsage; + ULONG NonPagedPoolUsage; +} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; + +//////////////////////////////////////////////////////////////////////////////// + +BOOL GetProcessFiles(ULONG process_id, + std::vector& files_vector) { + auto NtQuerySystemInformation = reinterpret_cast<_NtQuerySystemInformation>( + GetLibraryProcAddress("ntdll.dll", "NtQuerySystemInformation")); + auto NtDuplicateObject = reinterpret_cast<_NtDuplicateObject>( + GetLibraryProcAddress("ntdll.dll", "NtDuplicateObject")); + auto NtQueryObject = reinterpret_cast<_NtQueryObject>( + GetLibraryProcAddress("ntdll.dll", "NtQueryObject")); + + HANDLE processHandle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, process_id); + if (!processHandle) + return FALSE; + + NTSTATUS status; + ULONG handleInfoSize = 0x10000; + auto handleInfo = reinterpret_cast( + malloc(handleInfoSize)); + while ((status = NtQuerySystemInformation( + SystemHandleInformationEx, + handleInfo, + handleInfoSize, + nullptr)) == STATUS_INFO_LENGTH_MISMATCH) { + handleInfo = reinterpret_cast( + realloc(handleInfo, handleInfoSize *= 2)); + if (handleInfoSize > 16 * 1024 * 1024) + return FALSE; + } + if (!NT_SUCCESS(status)) + return FALSE; + + // Type index for files varies between OS versions + static unsigned short objectTypeFile = 0; + /* + switch (win::GetVersion()) { + case win::kVersionVista: + objectTypeFile = 25; + break; + case win::VERSION_WIN8: + objectTypeFile = 31; + break; + default: + objectTypeFile = 28; + break; + } + */ + + for (ULONG_PTR i = 0; i < handleInfo->HandleCount; i++) { + SYSTEM_HANDLE_EX handle = handleInfo->Handles[i]; + HANDLE dupHandle = nullptr; + POBJECT_TYPE_INFORMATION objectTypeInfo; + PVOID objectNameInfo; + UNICODE_STRING objectName; + ULONG returnLength; + + // Check if this handle belongs to the PID the user specified + if (reinterpret_cast(handle.ProcessId) != process_id) + continue; + // Skip if the handle does not belong to a file + if (objectTypeFile > 0 && handle.ObjectTypeIndex != objectTypeFile) + continue; + // Skip access codes that can cause NtDuplicateObject() or NtQueryObject() + // to hang + if (handle.GrantedAccess == 0x00100000 || + handle.GrantedAccess == 0x00120189 || + handle.GrantedAccess == 0x0012019f || + handle.GrantedAccess == 0x001a019f) + continue; + + // Duplicate the handle so we can query it + if (!NT_SUCCESS(NtDuplicateObject(processHandle, handle.Handle, + GetCurrentProcess(), &dupHandle, + 0, 0, 0))) { + continue; + } + // Query the object type + objectTypeInfo = reinterpret_cast(malloc(0x1000)); + if (!NT_SUCCESS(NtQueryObject(dupHandle, ObjectTypeInformation, + objectTypeInfo, 0x1000, NULL))) { + CloseHandle(dupHandle); + continue; + } + // Determine the type index for files + if (objectTypeFile == 0) { + std::wstring type_name(objectTypeInfo->Name.Buffer, + objectTypeInfo->Name.Length / 2); + if (IsEqual(type_name, L"File")) { + objectTypeFile = handle.ObjectTypeIndex; + LOG(LevelDebug, L"objectTypeFile is set to " + + ToWstr(objectTypeFile) + L"."); + } else { + continue; + } + } + + objectNameInfo = malloc(0x1000); + + UINT errorMode = SetErrorMode(SEM_FAILCRITICALERRORS); + SetErrorMode(errorMode | SEM_FAILCRITICALERRORS); + + if (!NT_SUCCESS(NtQueryObject(dupHandle, ObjectNameInformation, + objectNameInfo, 0x1000, &returnLength))) { + if (returnLength > 0x10000) { + free(objectTypeInfo); + free(objectNameInfo); + CloseHandle(dupHandle); + SetErrorMode(errorMode); + continue; + } + // Reallocate the buffer and try again + objectNameInfo = realloc(objectNameInfo, returnLength); + if (!NT_SUCCESS(NtQueryObject(dupHandle, ObjectNameInformation, + objectNameInfo, returnLength, NULL))) { + free(objectTypeInfo); + free(objectNameInfo); + CloseHandle(dupHandle); + SetErrorMode(errorMode); + continue; + } + } + + DWORD errorCode = GetLastError(); + if (errorCode != ERROR_SUCCESS) { + SetLastError(ERROR_SUCCESS); + } + SetErrorMode(errorMode); + + // Cast our buffer into a UNICODE_STRING + objectName = *reinterpret_cast(objectNameInfo); + // Add file path to our list + if (objectName.Length) { + std::wstring object_name(objectName.Buffer, objectName.Length / 2); + files_vector.push_back(object_name); + } + + free(objectTypeInfo); + free(objectNameInfo); + CloseHandle(dupHandle); + } + + free(handleInfo); + CloseHandle(processHandle); + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool CheckInstance(LPCWSTR mutex_name, LPCWSTR class_name) { + if (CreateMutex(NULL, FALSE, mutex_name) == NULL || + GetLastError() == ERROR_ALREADY_EXISTS || + GetLastError() == ERROR_ACCESS_DENIED) { + HWND hwnd = FindWindow(class_name, NULL); + + if (IsWindow(hwnd)) { + ActivateWindow(hwnd); + FlashWindow(hwnd, TRUE); + } + + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + +void ActivateWindow(HWND hwnd) { + if (IsIconic(hwnd)) + SendMessage(hwnd, WM_SYSCOMMAND, SC_RESTORE, NULL); + + if (!IsWindowVisible(hwnd)) + ShowWindow(hwnd, SW_SHOWNORMAL); + + SetForegroundWindow(hwnd); + BringWindowToTop(hwnd); +} + +std::wstring GetWindowClass(HWND hwnd) { + WCHAR buff[MAX_PATH]; + GetClassName(hwnd, buff, MAX_PATH); + return buff; +} + +std::wstring GetWindowTitle(HWND hwnd) { + WCHAR buff[MAX_PATH]; + GetWindowText(hwnd, buff, MAX_PATH); + return buff; +} + +std::wstring GetWindowPath(HWND hwnd) { + DWORD dwProcessId; + DWORD dwSize = MAX_PATH; + WCHAR buff[MAX_PATH] = {'\0'}; + + GetWindowThreadProcessId(hwnd, &dwProcessId); + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, + FALSE, dwProcessId); + + typedef DWORD (WINAPI *_QueryFullProcessImageName)( + HANDLE hProcess, DWORD dwFlags, LPTSTR lpExeName, PDWORD lpdwSize); + + if (hProcess != NULL) { + bool success = false; + if (win::GetVersion() >= win::kVersionVista) { + HMODULE hKernel32 = LoadLibrary(L"kernel32.dll"); + if (hKernel32 != NULL) { + auto proc = reinterpret_cast<_QueryFullProcessImageName>( + GetProcAddress(hKernel32, "QueryFullProcessImageNameW")); + if (proc != NULL) { + success = (proc)(hProcess, 0, buff, &dwSize) != 0; + } + FreeLibrary(hKernel32); + } + } + if (!success) { + GetModuleFileNameEx(hProcess, NULL, buff, dwSize); + } + CloseHandle(hProcess); + } + + return buff; +} + +bool IsFullscreen(HWND hwnd) { + LONG style = GetWindowLong(hwnd, GWL_EXSTYLE); + + if (style & WS_EX_TOPMOST) { + RECT rect; + GetClientRect(hwnd, &rect); + + if (rect.right >= GetSystemMetrics(SM_CXSCREEN) && + rect.bottom >= GetSystemMetrics(SM_CYSCREEN)) + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + +PVOID GetLibraryProcAddress(PSTR dll_module, PSTR proc_name) { + return GetProcAddress(GetModuleHandleA(dll_module), proc_name); +} + +bool TranslateDeviceName(std::wstring& path) { + size_t pos = path.find('\\', 8); + if (pos == std::wstring::npos) + return false; + std::wstring device_name = path.substr(0, pos); + path = path.substr(pos); + + const int size = 1024; + WCHAR szTemp[size] = {'\0'}; + if (!GetLogicalDriveStrings(size - 1, szTemp)) + return false; + + WCHAR* p = szTemp; + bool bFound = false; + WCHAR szName[MAX_PATH]; + WCHAR szDrive[3] = L" :"; + + do { + *szDrive = *p; + if (QueryDosDevice(szDrive, szName, MAX_PATH)) { + if (device_name == szName) { + device_name = szDrive; + bFound = true; + } + } + while (*p++); + } while (!bFound && *p); + + path = device_name + path; + return bFound; } \ No newline at end of file diff --git a/process.h b/src/base/process.h similarity index 60% rename from process.h rename to src/base/process.h index 409b51cb8..7ec5ec498 100644 --- a/process.h +++ b/src/base/process.h @@ -1,35 +1,39 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef PROCESS_H -#define PROCESS_H - -#include "std.h" - -// ============================================================================= - -void ActivateWindow(HWND hwnd); -bool CheckInstance(LPCWSTR mutex_name, LPCWSTR class_name); -BOOL GetProcessFiles(ULONG process_id, vector& files_vector); -wstring GetWindowClass(HWND hwnd); -wstring GetWindowPath(HWND hwnd); -wstring GetWindowTitle(HWND hwnd); -bool IsFullscreen(HWND hwnd); -bool TranslateDeviceName(wstring& path); - -#endif // PROCESS_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_PROCESS_H +#define TAIGA_BASE_PROCESS_H + +#include +#include +#include + +BOOL GetProcessFiles(ULONG process_id, std::vector& files_vector); + +bool CheckInstance(LPCWSTR mutex_name, LPCWSTR class_name); + +void ActivateWindow(HWND hwnd); +std::wstring GetWindowClass(HWND hwnd); +std::wstring GetWindowPath(HWND hwnd); +std::wstring GetWindowTitle(HWND hwnd); +bool IsFullscreen(HWND hwnd); + +PVOID GetLibraryProcAddress(PSTR dll_module, PSTR proc_name); +bool TranslateDeviceName(std::wstring& path); + +#endif // TAIGA_BASE_PROCESS_H \ No newline at end of file diff --git a/src/base/settings.cpp b/src/base/settings.cpp new file mode 100644 index 000000000..113f1727c --- /dev/null +++ b/src/base/settings.cpp @@ -0,0 +1,140 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "settings.h" +#include "string.h" +#include "xml.h" + +namespace base { + +Setting::Setting(bool attribute, + const std::wstring& path) + : attribute(attribute), + path(path) { +} + +Setting::Setting(bool attribute, + const std::wstring& default_value, + const std::wstring& path) + : attribute(attribute), + default_value(default_value), + path(path) { +} + +//////////////////////////////////////////////////////////////////////////////// + +const std::wstring& Settings::operator[](enum_t name) const { + return GetWstr(name); +} + +bool Settings::GetBool(enum_t name) const { + auto it = map_.find(name); + + if (it != map_.end()) + return ToBool(it->second.value); + + return false; +} + +int Settings::GetInt(enum_t name) const { + auto it = map_.find(name); + + if (it != map_.end()) + return ToInt(it->second.value); + + return 0; +} + +const std::wstring& Settings::GetWstr(enum_t name) const { + auto it = map_.find(name); + + if (it != map_.end()) + return it->second.value; + + return EmptyString(); +} + +void Settings::Set(enum_t name, bool value) { + map_[name].value = value ? L"true" : L"false"; +} + +void Settings::Set(enum_t name, int value) { + map_[name].value = ToWstr(value); +} + +void Settings::Set(enum_t name, const std::wstring& value) { + map_[name].value = value; +} + +//////////////////////////////////////////////////////////////////////////////// + +void Settings::InitializeKey(enum_t name, const wchar_t* default_value, + const std::wstring& path) { + if (default_value) { + map_.insert(std::make_pair(name, base::Setting(true, default_value, path))); + } else { + map_.insert(std::make_pair(name, base::Setting(true, path))); + } +} + +void Settings::ReadValue(const xml_node& node_parent, enum_t name) { + Setting& item = map_[name]; + + std::vector node_names; + Split(item.path, L"/", node_names); + + const wchar_t* node_name = node_names.back().c_str(); + + xml_node current_node = node_parent; + for (int i = 0; i < static_cast(node_names.size()) - 1; i++) + current_node = current_node.child(node_names.at(i).c_str()); + + if (item.attribute) { + const wchar_t* default_value = item.default_value.c_str(); + item.value = current_node.attribute(node_name).as_string(default_value); + } else { + item.value = XmlReadStrValue(current_node, node_name); + } +} + +void Settings::WriteValue(const xml_node& node_parent, enum_t name) { + Setting& item = map_[name]; + + std::vector node_names; + Split(item.path, L"/", node_names); + + const wchar_t* node_name = node_names.back().c_str(); + + xml_node current_node = node_parent; + for (int i = 0; i < static_cast(node_names.size()) - 1; i++) { + std::wstring child_name = node_names.at(i); + if (!current_node.child(child_name.c_str())) { + current_node = current_node.append_child(child_name.c_str()); + } else { + current_node = current_node.child(child_name.c_str()); + } + } + + if (item.attribute) { + current_node.append_attribute(node_name) = item.value.c_str(); + } else { + XmlWriteStrValue(current_node, node_name, item.value.c_str()); + } +} + +} // namespace base \ No newline at end of file diff --git a/src/base/settings.h b/src/base/settings.h new file mode 100644 index 000000000..11198aa52 --- /dev/null +++ b/src/base/settings.h @@ -0,0 +1,70 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_SETTINGS_H +#define TAIGA_BASE_SETTINGS_H + +#include +#include + +#include "types.h" + +namespace pugi { +class xml_node; +} + +namespace base { + +class Setting { +public: + Setting() {} + Setting(bool attribute, const std::wstring& path); + Setting(bool attribute, const std::wstring& default_value, const std::wstring& path); + ~Setting() {} + + bool attribute; + std::wstring default_value; + std::wstring path; + std::wstring value; +}; + +class Settings { +public: + const std::wstring& operator[](enum_t name) const; + + bool GetBool(enum_t name) const; + int GetInt(enum_t name) const; + const std::wstring& GetWstr(enum_t name) const; + + void Set(enum_t name, bool value); + void Set(enum_t name, int value); + void Set(enum_t name, const std::wstring& value); + +protected: + void InitializeKey(enum_t name, const wchar_t* default_value, const std::wstring& path); + void ReadValue(const pugi::xml_node& node_parent, enum_t name); + void WriteValue(const pugi::xml_node& node_parent, enum_t name); + + virtual void InitializeMap() = 0; + + std::map map_; +}; + +} // namespace base + +#endif // TAIGA_BASE_SETTINGS_H \ No newline at end of file diff --git a/src/base/string.cpp b/src/base/string.cpp new file mode 100644 index 000000000..414f131df --- /dev/null +++ b/src/base/string.cpp @@ -0,0 +1,813 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include + +#include "string.h" + +using std::string; +using std::vector; +using std::wstring; + +//////////////////////////////////////////////////////////////////////////////// +// Erasing + +void Erase(wstring& str1, const wstring& str2, bool case_insensitive) { + if (str2.empty() || str1.length() < str2.length()) + return; + + auto it = str1.begin(); + + do { + if (case_insensitive) { + it = std::search(it, str1.end(), str2.begin(), str2.end(), &IsCharsEqual); + } else { + it = std::search(it, str1.end(), str2.begin(), str2.end()); + } + + if (it != str1.end()) + str1.erase(it, it + str2.length()); + + } while (it != str1.end()); +} + +void EraseChars(wstring& str, const wchar_t chars[]) { + size_t pos = 0; + + do { + pos = str.find_first_of(chars, pos); + + if (pos != wstring::npos) + str.erase(pos, 1); + + } while (pos != wstring::npos); +} + +void ErasePunctuation(wstring& str, bool keep_trailing) { + auto rlast = str.rbegin(); + + if (keep_trailing) + rlast = std::find_if(str.rbegin(), str.rend(), + [](wchar_t c) -> bool { + return !(c == L'!' || // "Hayate no Gotoku!", "K-ON!"... + c == L'+' || // "Needless+" + c == L'\''); // "Gintama'" + }); + + auto it = std::remove_if(str.begin(), rlast.base(), + [](int c) -> bool { + // Control codes, white-space and punctuation characters + if (c <= 255 && !isalnum(c)) + return true; + // Unicode stars, hearts, notes, etc. (0x2000-0x2767) + if (c > 8192 && c < 10087) + return true; + // Valid character + return false; + }); + + if (keep_trailing) + std::copy(rlast.base(), str.end(), it); + + str.resize(str.size() - (rlast.base() - it)); +} + +void EraseLeft(wstring& str1, const wstring& str2, bool case_insensitive) { + if (str1.length() < str2.length()) + return; + + if (case_insensitive) { + if (!std::equal(str2.begin(), str2.end(), str1.begin(), &IsCharsEqual)) + return; + } else { + if (!std::equal(str2.begin(), str2.end(), str1.begin())) + return; + } + + str1.erase(str1.begin(), str1.begin() + str2.length()); +} + +void EraseRight(wstring& str1, const wstring& str2, bool case_insensitive) { + if (str1.length() < str2.length()) + return; + + if (case_insensitive) { + if (!std::equal(str2.begin(), str2.end(), str1.end() - str2.length(), + &IsCharsEqual)) + return; + } else { + if (!std::equal(str2.begin(), str2.end(), str1.end() - str2.length())) + return; + } + + str1.resize(str1.length() - str2.length()); +} + +void RemoveEmptyStrings(vector& input) { + if (input.empty()) + return; + + input.erase(std::remove_if(input.begin(), input.end(), + [](const wstring& s) -> bool { return s.empty(); }), + input.end()); +} + +//////////////////////////////////////////////////////////////////////////////// +// Searching and comparison + +wstring CharLeft(const wstring& str, int length) { + return str.substr(0, length); +} + +wstring CharRight(const wstring& str, int length) { + if (length > static_cast(str.length())) { + return str.substr(0, str.length()); + } else { + return str.substr(str.length() - length, length); + } +} + +int CompareStrings(const wstring& str1, const wstring& str2, + bool case_insensitive, size_t max_count) { + if (case_insensitive) { + return _wcsnicmp(str1.c_str(), str2.c_str(), max_count); + } else { + return wcsncmp(str1.c_str(), str2.c_str(), max_count); + } +} + +int InStr(const wstring& str1, const wstring& str2, int pos, + bool case_insensitive) { + if (str1.empty()) + return -1; + if (str2.empty()) + return 0; + if (str1.length() < str2.length()) + return -1; + + if (case_insensitive) { + auto i = std::search(str1.begin() + pos, str1.end(), + str2.begin(), str2.end(), + &IsCharsEqual); + return (i == str1.end()) ? -1 : i - str1.begin(); + } else { + size_t i = str1.find(str2, pos); + return (i != wstring::npos) ? i : -1; + } +} + +wstring InStr(const wstring& str1, const wstring& str2_left, + const wstring& str2_right) { + wstring output; + + int index_begin = InStr(str1, str2_left); + + if (index_begin > -1) { + index_begin += str2_left.length(); + int index_end = InStr(str1, str2_right, index_begin); + if (index_end > -1) + output = str1.substr(index_begin, index_end - index_begin); + } + + return output; +} + +int InStrRev(const wstring& str1, const wstring& str2, int pos) { + size_t i = str1.rfind(str2, pos); + return (i != wstring::npos) ? i : -1; +} + +int InStrChars(const wstring& str1, const wstring& str2, int pos) { + size_t i = str1.find_first_of(str2, pos); + return (i != wstring::npos) ? i : -1; +} + +int InStrCharsRev(const wstring& str1, const wstring& str2, int pos) { + size_t i = str1.find_last_of(str2, pos); + return (i != wstring::npos) ? i : -1; +} + +bool IsAlphanumeric(const wchar_t c) { + return (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z'); +} +bool IsAlphanumeric(const wstring& str) { + if (str.empty()) + return false; + + for (size_t i = 0; i < str.length(); i++) + if (!IsAlphanumeric(str[i])) + return false; + + return true; +} + +inline bool IsCharsEqual(const wchar_t c1, const wchar_t c2) { + return tolower(c1) == tolower(c2); +} + +bool IsEqual(const wstring& str1, const wstring& str2) { + if (str1.length() != str2.length()) + return false; + + return std::equal(str1.begin(), str1.end(), str2.begin(), &IsCharsEqual); +} + +bool IsHex(const wchar_t c) { + return (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F') || + (c >= 'a' && c <= 'f'); +} +bool IsHex(const wstring& str) { + if (str.empty()) + return false; + + for (size_t i = 0; i < str.length(); i++) + if (!IsHex(str[i])) + return false; + + return true; +} + +bool IsNumeric(const wchar_t c) { + return c >= '0' && c <= '9'; +} +bool IsNumeric(const wstring& str) { + if (str.empty()) + return false; + + for (size_t i = 0; i < str.length(); i++) + if (!IsNumeric(str[i])) + return false; + + return true; +} + +bool IsWhitespace(const wchar_t c) { + return c == ' ' || c == '\r' || c == '\n' || c == '\t'; +} + +bool StartsWith(const wstring& str1, const wstring& str2) { + return str1.compare(0, str2.length(), str2) == 0; +} + +bool EndsWith(const wstring& str1, const wstring& str2) { + if (str2.length() > str1.length()) + return false; + + return str1.compare(str1.length() - str2.length(), str2.length(), str2) == 0; +} + +size_t LongestCommonSubsequenceLength(const wstring& str1, + const wstring& str2) { + if (str1.empty() || str2.empty()) + return 0; + + const size_t len1 = str1.length(); + const size_t len2 = str2.length(); + + vector> table(len1 + 1); + for (auto it = table.begin(); it != table.end(); ++it) + it->resize(len2 + 1); + + for (size_t i = 0; i < len1; i++) { + for (size_t j = 0; j < len2; j++) { + if (str1[i] == str2[j]) { + table[i + 1][j + 1] = table[i][j] + 1; + } else { + table[i + 1][j + 1] = max(table[i + 1][j], table[i][j + 1]); + } + } + } + + return table.back().back(); +} + +size_t LongestCommonSubstringLength(const wstring& str1, const wstring& str2) { + if (str1.empty() || str2.empty()) + return 0; + + const size_t len1 = str1.length(); + const size_t len2 = str2.length(); + + vector> table(len1); + for (auto it = table.begin(); it != table.end(); ++it) + it->resize(len2); + + size_t longest_length = 0; + + for (size_t i = 0; i < len1; i++) { + for (size_t j = 0; j < len2; j++) { + if (str1[i] == str2[j]) { + if (i == 0 || j == 0) { + table[i][j] = 1; + } else { + table[i][j] = table[i - 1][j - 1] + 1; + } + if (table[i][j] > longest_length) { + longest_length = table[i][j]; + } + } else { + table[i][j] = 0; + } + } + } + + return longest_length; +} + +size_t LevenshteinDistance(const wstring& str1, const wstring& str2) { + const size_t len1 = str1.size(); + const size_t len2 = str2.size(); + + vector prev_col(len2 + 1); + for (size_t i = 0; i < prev_col.size(); i++) + prev_col[i] = i; + + vector col(len2 + 1); + + for (size_t i = 0; i < len1; i++) { + col[0] = i + 1; + + for (size_t j = 0; j < len2; j++) + col[j + 1] = min(min(1 + col[j], 1 + prev_col[1 + j]), + prev_col[j] + (str1[i] == str2[j] ? 0 : 1)); + + col.swap(prev_col); + } + + return prev_col[len2]; +} + +//////////////////////////////////////////////////////////////////////////////// +// Replace + +void Replace(wstring& input, wstring find, wstring replace_with, + bool replace_all, bool case_insensitive) { + if (find.empty() || find == replace_with || input.length() < find.length()) + return; + + if (!case_insensitive) { + for (size_t pos = input.find(find); pos != wstring::npos; + pos = input.find(find, pos)) { + input.replace(pos, find.length(), replace_with); + if (!replace_all) + pos += replace_with.length(); + } + } else { + for (size_t i = 0; i < input.length() - find.length() + 1; i++) { + for (size_t j = 0; j < find.length(); j++) { + if (input.length() < find.length()) + return; + if (tolower(input[i + j]) == tolower(find[j])) { + if (j == find.length() - 1) { + input.replace(i--, find.length(), replace_with); + if (!replace_all) + i += replace_with.length(); + break; + } + } else { + i += j; + break; + } + } + } + } +} + +void ReplaceChar(wstring& str, const wchar_t c, const wchar_t replace_with) { + if (c == replace_with) + return; + + size_t pos = 0; + + do { + pos = str.find_first_of(c, pos); + if (pos != wstring::npos) + str.at(pos) = replace_with; + } while (pos != wstring::npos); +} + +void ReplaceChars(wstring& str, const wchar_t chars[], + const wstring replace_with) { + if (chars == replace_with) + return; + + size_t pos = 0; + + do { + pos = str.find_first_of(chars, pos); + if (pos != wstring::npos) + str.replace(pos, 1, replace_with); + } while (pos != wstring::npos); +} + +//////////////////////////////////////////////////////////////////////////////// +// Split, tokenize + +wstring Join(const vector& join_vector, const wstring& separator) { + wstring str; + + for (auto it = join_vector.begin(); it != join_vector.end(); ++it) { + if (it != join_vector.begin()) + str += separator; + str += *it; + } + + return str; +} + +void Split(const wstring& str, const wstring& separator, + std::vector& split_vector) { + if (separator.empty()) { + split_vector.push_back(str); + return; + } + + size_t index_begin = 0, index_end; + + do { + index_end = str.find(separator, index_begin); + if (index_end == wstring::npos) + index_end = str.length(); + + split_vector.push_back(str.substr(index_begin, index_end - index_begin)); + + index_begin = index_end + separator.length(); + } while (index_begin <= str.length()); +} + +wstring SubStr(const wstring& str, const wstring& sub_begin, + const wstring& sub_end) { + size_t index_begin = str.find(sub_begin, 0); + if (index_begin == wstring::npos) + return L""; + + size_t index_end = str.find(sub_end, index_begin); + if (index_end == wstring::npos) + index_end = str.length(); + + return str.substr(index_begin + 1, index_end - index_begin - 1); +} + +size_t Tokenize(const wstring& str, const wstring& delimiters, + vector& tokens) { + tokens.clear(); + + size_t index_begin = str.find_first_not_of(delimiters); + + while (index_begin != wstring::npos) { + size_t index_end = str.find_first_of(delimiters, index_begin + 1); + if (index_end == wstring::npos) { + tokens.push_back(str.substr(index_begin)); + break; + } else { + tokens.push_back(str.substr(index_begin, index_end - index_begin)); + index_begin = str.find_first_not_of(delimiters, index_end + 1); + } + } + + return tokens.size(); +} + +//////////////////////////////////////////////////////////////////////////////// +// std::string <-> std::wstring conversion + +wstring StrToWstr(const string& str, UINT code_page) { + if (!str.empty()) { + int length = MultiByteToWideChar(code_page, 0, str.c_str(), -1, nullptr, 0); + if (length > 0) { + vector output(length); + MultiByteToWideChar(code_page, 0, str.c_str(), -1, &output[0], length); + return &output[0]; + } + } + + return wstring(); +} + +string WstrToStr(const wstring& str, UINT code_page) { + if (!str.empty()) { + int length = WideCharToMultiByte(code_page, 0, str.c_str(), -1, nullptr, 0, + nullptr, nullptr); + if (length > 0) { + vector output(length); + WideCharToMultiByte(code_page, 0, str.c_str(), -1, &output[0], length, + nullptr, nullptr); + return &output[0]; + } + } + + return string(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Case conversion + +// System-default ANSI code page +std::locale current_locale(""); + +void ToLower(wstring& str, bool use_locale) { + if (use_locale) { + std::transform(str.begin(), str.end(), str.begin(), + std::bind2nd(std::ptr_fun(&std::tolower), + current_locale)); + } else { + std::transform(str.begin(), str.end(), str.begin(), towlower); + } +} + +wstring ToLower_Copy(wstring str, bool use_locale) { + ToLower(str, use_locale); + return str; +} + +void ToUpper(wstring& str, bool use_locale) { + if (use_locale) { + std::transform(str.begin(), str.end(), str.begin(), + std::bind2nd(std::ptr_fun(&std::toupper), + current_locale)); + } else { + std::transform(str.begin(), str.end(), str.begin(), towupper); + } +} + +wstring ToUpper_Copy(wstring str, bool use_locale) { + ToUpper(str, use_locale); + return str; +} + +//////////////////////////////////////////////////////////////////////////////// +// Type conversion + +bool ToBool(const wstring& str) { + if (str.empty()) + return false; + + const wchar_t c = str.front(); + + return (c == '1' || c == 't' || c == 'T' || c == 'y' || c == 'Y'); +} + +double ToDouble(const wstring& str) { + return _wtof(str.c_str()); +} + +int ToInt(const wstring& str) { + return _wtoi(str.c_str()); +} + +wstring ToWstr(const INT& value) { + wchar_t buffer[65]; + _ltow_s(value, buffer, 65, 10); + return wstring(buffer); +} + +wstring ToWstr(const ULONG& value) { + wchar_t buffer[65]; + _ultow_s(value, buffer, 65, 10); + return wstring(buffer); +} + +wstring ToWstr(const INT64& value) { + wchar_t buffer[65]; + _i64tow_s(value, buffer, 65, 10); + return wstring(buffer); +} + +wstring ToWstr(const UINT64& value) { + wchar_t buffer[65]; + _ui64tow_s(value, buffer, 65, 10); + return wstring(buffer); +} + +wstring ToWstr(const double& value, int count) { + std::wostringstream out; + out << std::fixed << std::setprecision(count) << value; + return out.str(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Trimming + +wstring LimitText(const wstring& str, unsigned int limit, const wstring& tail) { + if (str.length() > limit) { + wstring limit_str = str.substr(0, limit); + if (!tail.empty() && limit_str.length() > tail.length()) + limit_str.replace(limit_str.length() - tail.length(), + tail.length(), tail); + return limit_str; + } else { + return str; + } +} + +void Trim(wstring& str, const wchar_t trim_chars[], + bool trim_left, bool trim_right) { + if (str.empty()) + return; + + const size_t index_begin = + trim_left ? str.find_first_not_of(trim_chars) : 0; + const size_t index_end = + trim_right ? str.find_last_not_of(trim_chars) : str.length() - 1; + + if (index_begin == wstring::npos || index_end == wstring::npos) { + str.clear(); + return; + } + + if (trim_right) + str.erase(index_end + 1, str.length() - index_end + 1); + if (trim_left) + str.erase(0, index_begin); +} + +void TrimLeft(wstring& str, const wchar_t trim_chars[]) { + Trim(str, trim_chars, true, false); +} + +void TrimRight(wstring& str, const wchar_t trim_chars[]) { + Trim(str, trim_chars, false, true); +} + +//////////////////////////////////////////////////////////////////////////////// +// File and folder related + +void AddTrailingSlash(wstring& str) { + if (str.length() > 0 && str[str.length() - 1] != '\\') + str += L"\\"; +} + +wstring AddTrailingSlash(const wstring& str) { + if (str.length() > 0 && str[str.length() - 1] != '\\') { + return str + L"\\"; + } else { + return str; + } +} + +bool CheckFileExtension(wstring extension, const vector& extension_list) { + if (extension.empty() || extension_list.empty()) + return false; + + for (size_t i = 0; i < extension.length(); i++) + extension[i] = toupper(extension[i]); + + for (size_t i = 0; i < extension_list.size(); i++) + if (extension == extension_list[i]) + return true; + + return false; +} + +wstring GetFileExtension(const wstring& str) { + return str.substr(str.find_last_of(L".") + 1); +} + +wstring GetFileWithoutExtension(wstring str) { + size_t pos = str.find_last_of(L"."); + + if (pos != wstring::npos) + str.resize(pos); + + return str; +} + +wstring GetFileName(const wstring& str) { + return str.substr(str.find_last_of(L"/\\") + 1); +} + +wstring GetPathOnly(const wstring& str) { + return str.substr(0, str.find_last_of(L"/\\") + 1); +} + +bool ValidateFileExtension(const wstring& extension, unsigned int max_length) { + if (max_length > 0 && extension.length() > max_length) + return false; + + if (!IsAlphanumeric(extension)) + return false; + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// Other + +void AppendString(wstring& str0, const wstring& str1, const wstring& str2) { + if (str1.empty()) + return; + + if (!str0.empty()) + str0.append(str2); + + str0.append(str1); +} + +const wstring& EmptyString() { + static const wstring str; + return str; +} + +wstring PadChar(wstring str, const wchar_t ch, const size_t len) { + if (len > str.length()) + str.insert(0, len - str.length(), ch); + + return str; +} + +wstring PushString(const wstring& str1, const wstring& str2) { + if (str2.empty()) { + return L""; + } else { + return str1 + str2; + } +} + +void ReadStringFromResource(LPCWSTR name, LPCWSTR type, wstring& output) { + HRSRC hResInfo = FindResource(nullptr, name, type); + HGLOBAL hResHandle = LoadResource(nullptr, hResInfo); + DWORD dwSize = SizeofResource(nullptr, hResInfo); + + const char* lpData = static_cast(LockResource(hResHandle)); + string temp(lpData, dwSize); + output = StrToWstr(temp); + + FreeResource(hResInfo); +} + +//////////////////////////////////////////////////////////////////////////////// +// Returns the most common non-alphanumeric character in a string that is +// included in the table. Note that characters in the table are listed by their +// precedence. + +const wstring kCommonCharTable = L",_ .-+;&|~"; + +int GetCommonCharIndex(wchar_t c) { + for (size_t i = 0; i < kCommonCharTable.size(); i++) + if (kCommonCharTable.at(i) == c) + return i; + + return -1; +} + +wchar_t GetMostCommonCharacter(wstring str) { + Trim(str); + + std::map frequency; + + for (auto it = str.begin(); it != str.end(); ++it) { + if (IsAlphanumeric(*it)) + continue; + if (GetCommonCharIndex(*it) == -1) + continue; + + frequency[*it] += 1; + } + + wchar_t most_common_char = L'\0'; + + for (auto it = frequency.begin(); it != frequency.end(); ++it) { + if (most_common_char == L'\0') { + most_common_char = it->first; + continue; + } + + int character_distance = GetCommonCharIndex(it->first) - + GetCommonCharIndex(most_common_char); + if (character_distance < 0) { + most_common_char = it->first; + continue; + } + + float frequency_ratio = static_cast(it->second) / + static_cast(frequency[most_common_char]); + if (frequency_ratio / character_distance > 0.8f) { + most_common_char = it->first; + } + } + + return most_common_char; +} \ No newline at end of file diff --git a/src/base/string.h b/src/base/string.h new file mode 100644 index 000000000..0dca8078f --- /dev/null +++ b/src/base/string.h @@ -0,0 +1,109 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_STRING_H +#define TAIGA_BASE_STRING_H + +#include +#include +#include + +void Erase(std::wstring& str1, const std::wstring& str2, bool case_insensitive = false); +void EraseChars(std::wstring& str, const wchar_t chars[]); +void ErasePunctuation(std::wstring& str, bool keep_trailing = false); +void EraseLeft(std::wstring& str1, const std::wstring& str2, bool case_insensitive = false); +void EraseRight(std::wstring& str1, const std::wstring& str2, bool case_insensitive = false); +void RemoveEmptyStrings(std::vector& input); + +std::wstring CharLeft(const std::wstring& str, int length); +std::wstring CharRight(const std::wstring& str, int length); + +int CompareStrings(const std::wstring& str1, const std::wstring& str2, bool case_insensitive = true, size_t max_count = MAX_PATH); +inline bool IsCharsEqual(const wchar_t c1, const wchar_t c2); +bool IsEqual(const std::wstring& str1, const std::wstring& str2); + +int InStr(const std::wstring& str1, const std::wstring& str2, int pos = 0, bool case_insensitive = false); +std::wstring InStr(const std::wstring& str1, const std::wstring& str2_left, const std::wstring& str2_right); +int InStrRev(const std::wstring& str1, const std::wstring& str2, int pos); +int InStrChars(const std::wstring& str1, const std::wstring& str2, int pos); +int InStrCharsRev(const std::wstring& str1, const std::wstring& str2, int pos); + +bool IsAlphanumeric(const wchar_t c); +bool IsAlphanumeric(const std::wstring& str); +bool IsHex(const wchar_t c); +bool IsHex(const std::wstring& str); +bool IsNumeric(const wchar_t c); +bool IsNumeric(const std::wstring& str); +bool IsWhitespace(const wchar_t c); + +bool StartsWith(const std::wstring& str, const std::wstring& search); +bool EndsWith(const std::wstring& str, const std::wstring& search); + +size_t LongestCommonSubsequenceLength(const std::wstring& str1, const std::wstring& str2); +size_t LongestCommonSubstringLength(const std::wstring& str1, const std::wstring& str2); +size_t LevenshteinDistance(const std::wstring& str1, const std::wstring& str2); + +void Replace(std::wstring& str1, std::wstring str2, std::wstring replace_with, bool replace_all = false, bool case_insensitive = false); +void ReplaceChar(std::wstring& str, const wchar_t c, const wchar_t replace_with); +void ReplaceChars(std::wstring& str, const wchar_t chars[], const std::wstring replace_with); + +std::wstring Join(const std::vector& join_vector, const std::wstring& separator); +void Split(const std::wstring& str, const std::wstring& separator, std::vector& split_vector); +std::wstring SubStr(const std::wstring& str, const std::wstring& sub_begin, const std::wstring& sub_end); +size_t Tokenize(const std::wstring& str, const std::wstring& delimiters, std::vector& tokens); + +std::wstring StrToWstr(const std::string& str, UINT code_page = CP_UTF8); +std::string WstrToStr(const std::wstring& str, UINT code_page = CP_UTF8); + +void ToLower(std::wstring& str, bool use_locale = false); +std::wstring ToLower_Copy(std::wstring str, bool use_locale = false); +void ToUpper(std::wstring& str, bool use_locale = false); +std::wstring ToUpper_Copy(std::wstring str, bool use_locale = false); + +bool ToBool(const std::wstring& str); +double ToDouble(const std::wstring& str); +int ToInt(const std::wstring& str); +std::wstring ToWstr(const INT& value); +std::wstring ToWstr(const ULONG& value); +std::wstring ToWstr(const INT64& value); +std::wstring ToWstr(const UINT64& value); +std::wstring ToWstr(const double& value, int count = 16); + +std::wstring LimitText(const std::wstring& str, unsigned int limit, const std::wstring& tail = L"..."); +void Trim(std::wstring& str, const wchar_t trim_chars[] = L" ", bool trim_left = true, bool trim_right = true); +void TrimLeft(std::wstring& str, const wchar_t trim_chars[] = L" "); +void TrimRight(std::wstring& str, const wchar_t trim_chars[] = L" "); + +void AddTrailingSlash(std::wstring& str); +std::wstring AddTrailingSlash(const std::wstring& str); +bool CheckFileExtension(std::wstring extension, const std::vector& extension_list); +std::wstring GetFileExtension(const std::wstring& str); +std::wstring GetFileName(const std::wstring& str); +std::wstring GetFileWithoutExtension(std::wstring str); +std::wstring GetPathOnly(const std::wstring& str); +bool ValidateFileExtension(const std::wstring& extension, unsigned int max_length = 0); + +void AppendString(std::wstring& str0, const std::wstring& str1, const std::wstring& str2 = L", "); +const std::wstring& EmptyString(); +std::wstring PadChar(std::wstring str, const wchar_t ch, const size_t len); +std::wstring PushString(const std::wstring& str1, const std::wstring& str2); +void ReadStringFromResource(LPCWSTR name, LPCWSTR type, std::wstring& output); + +wchar_t GetMostCommonCharacter(std::wstring str); + +#endif // TAIGA_BASE_STRING_H diff --git a/time.cpp b/src/base/time.cpp similarity index 51% rename from time.cpp rename to src/base/time.cpp index 0d49381fd..d6731d757 100644 --- a/time.cpp +++ b/src/base/time.cpp @@ -1,209 +1,198 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "time.h" - -#include "myanimelist.h" -#include "string.h" - -// ============================================================================= - -Date::Date() - : year(0), - month(0), - day(0) { -} - -Date::Date(const wstring& date) { - *this = mal::ParseDateString(date); -} - -Date::Date(unsigned short year, unsigned short month, unsigned short day) - : year(year), - month(month), - day(day) { -} - -Date& Date::operator = (const Date& date) { - year = date.year; - month = date.month; - day = date.day; - - return *this; -} - -bool Date::operator == (const Date& date) const { - return year == date.year && month == date.month && day == date.day; -} - -bool Date::operator != (const Date& date) const { - return !operator == (date); -} - -bool Date::operator < (const Date& date) const { - if (year && !date.year) return true; - if (!year && date.year) return false; - if (year != date.year) return year < date.year; - - if (month && !date.month) return true; - if (!month && date.month) return false; - if (month != date.month) return month < date.month; - - if (day && !date.day) return true; - if (!day && date.day) return false; - return day < date.day; -} - -bool Date::operator <= (const Date& date) const { - return !operator > (date); -} - -bool Date::operator >= (const Date& date) const { - return !operator < (date); -} - -bool Date::operator > (const Date& date) const { - if (!year && date.year) return true; - if (year && !date.year) return false; - if (year != date.year) return year > date.year; - - if (!month && date.month) return true; - if (month && !date.month) return false; - if (month != date.month) return month > date.month; - - if (!day && date.day) return true; - if (day && !date.day) return false; - return day > date.day; -} - -int Date::operator - (const Date& date) const { - return ((year * 365) + (month * 30) + day) - - ((date.year * 365) + (date.month * 30) + date.day); -} - -Date::operator bool() const { - return year != 0 || month != 0 || day != 0; -} - -Date::operator SYSTEMTIME() const { - SYSTEMTIME st; - st.wYear = year; - st.wMonth = month; - st.wDay = day; - return st; -} - -Date::operator wstring() const { - return PadChar(ToWstr(year), '0', 4) + L"-" + - PadChar(ToWstr(month), '0', 2) + L"-" + - PadChar(ToWstr(day), '0', 2); -} - -// ============================================================================= - -void GetSystemTime(SYSTEMTIME& st, int utc_offset) { - // Get current time, expressed in UTC - GetSystemTime(&st); - if (utc_offset == 0) return; - - // Convert to FILETIME - FILETIME ft; - SystemTimeToFileTime(&st, &ft); - // Convert to ULARGE_INTEGER - ULARGE_INTEGER ul; - ul.LowPart = ft.dwLowDateTime; - ul.HighPart = ft.dwHighDateTime; - - // Apply UTC offset - ul.QuadPart += static_cast(utc_offset) * 60 * 60 * 10000000; - - // Convert back to SYSTEMTIME - ft.dwLowDateTime = ul.LowPart; - ft.dwHighDateTime = ul.HighPart; - FileTimeToSystemTime(&ft, &st); -} - -Date GetDate() { - SYSTEMTIME st; - GetLocalTime(&st); - return Date(st.wYear, st.wMonth, st.wDay); -} - -wstring GetTime(LPCWSTR lpFormat) { - WCHAR buff[32]; - GetTimeFormat(LOCALE_SYSTEM_DEFAULT, 0, NULL, lpFormat, buff, 32); - return buff; -} - -Date GetDateJapan() { - SYSTEMTIME stJST; - GetSystemTime(stJST, 9); // JST is UTC+09 - return Date(stJST.wYear, stJST.wMonth, stJST.wDay); -} - -wstring GetTimeJapan(LPCWSTR lpFormat) { - WCHAR buff[32]; - SYSTEMTIME stJST; - GetSystemTime(stJST, 9); // JST is UTC+09 - GetTimeFormat(LOCALE_SYSTEM_DEFAULT, 0, &stJST, lpFormat, buff, 32); - return buff; -} - -wstring ToDateString(time_t seconds) { - time_t days, hours, minutes; - wstring date; - - if (seconds > 0) { - #define CALC_TIME(x, y) x = seconds / (y); seconds = seconds % (y); - CALC_TIME(days, 60 * 60 * 24); - CALC_TIME(hours, 60 * 60); - CALC_TIME(minutes, 60); - #undef CALC_TIME - date.clear(); - #define ADD_TIME(x, y) \ - if (x > 0) { \ - if (!date.empty()) date += L" "; \ - date += ToWstr(x) + y; \ - if (x > 1) date += L"s"; \ - } - ADD_TIME(days, L" day"); - ADD_TIME(hours, L" hour"); - ADD_TIME(minutes, L" minute"); - ADD_TIME(seconds, L" second"); - #undef ADD_TIME - } - - return date; -} - -unsigned int ToDayCount(const Date& date) { - return (date.year * 365) + (date.month * 30) + date.day; -} - -wstring ToTimeString(int seconds) { - int hours = seconds / 3600; - seconds = seconds % 3600; - int minutes = seconds / 60; - seconds = seconds % 60; - #define TWO_DIGIT(x) (x >= 10 ? ToWstr(x) : L"0" + ToWstr(x)) - return (hours > 0 ? TWO_DIGIT(hours) + L":" : L"") + - TWO_DIGIT(minutes) + L":" + TWO_DIGIT(seconds); - #undef TWO_DIGIT +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "string.h" +#include "time.h" + +Date::Date() + : year(0), month(0), day(0) { +} + +Date::Date(const std::wstring& date) + : year(0), month(0), day(0) { + // Convert from YYYY-MM-DD + if (date.length() >= 10) { + year = ToInt(date.substr(0, 4)); + month = ToInt(date.substr(5, 2)); + day = ToInt(date.substr(8, 2)); + } +} + +Date::Date(unsigned short year, unsigned short month, unsigned short day) + : year(year), month(month), day(day) { +} + +Date& Date::operator = (const Date& date) { + year = date.year; + month = date.month; + day = date.day; + + return *this; +} + +int Date::operator - (const Date& date) const { + return ((year * 365) + (month * 30) + day) - + ((date.year * 365) + (date.month * 30) + date.day); +} + +Date::operator bool() const { + return year != 0 || month != 0 || day != 0; +} + +Date::operator SYSTEMTIME() const { + SYSTEMTIME st; + st.wYear = year; + st.wMonth = month; + st.wDay = day; + + return st; +} + +Date::operator std::wstring() const { + // Convert to YYYY-MM-DD + return PadChar(ToWstr(year), '0', 4) + L"-" + + PadChar(ToWstr(month), '0', 2) + L"-" + + PadChar(ToWstr(day), '0', 2); +} + +base::CompareResult Date::Compare(const Date& date) const { + if (year != date.year) { + if (year == 0) + return base::kGreaterThan; + if (date.year == 0) + return base::kLessThan; + return year < date.year ? base::kLessThan : base::kGreaterThan; + } + + if (month != date.month) { + if (month == 0) + return base::kGreaterThan; + if (date.month == 0) + return base::kLessThan; + return month < date.month ? base::kLessThan : base::kGreaterThan; + } + + if (day != date.day) { + if (day == 0) + return base::kGreaterThan; + if (date.day == 0) + return base::kLessThan; + return day < date.day ? base::kLessThan : base::kGreaterThan; + } + + return base::kEqualTo; +} + +//////////////////////////////////////////////////////////////////////////////// + +void GetSystemTime(SYSTEMTIME& st, int utc_offset) { + // Get current time, expressed in UTC + GetSystemTime(&st); + if (utc_offset == 0) + return; + + // Convert to FILETIME + FILETIME ft; + SystemTimeToFileTime(&st, &ft); + // Convert to ULARGE_INTEGER + ULARGE_INTEGER ul; + ul.LowPart = ft.dwLowDateTime; + ul.HighPart = ft.dwHighDateTime; + + // Apply UTC offset + ul.QuadPart += static_cast(utc_offset) * 60 * 60 * 10000000; + + // Convert back to SYSTEMTIME + ft.dwLowDateTime = ul.LowPart; + ft.dwHighDateTime = ul.HighPart; + FileTimeToSystemTime(&ft, &st); +} + +Date GetDate() { + SYSTEMTIME st; + GetLocalTime(&st); + return Date(st.wYear, st.wMonth, st.wDay); +} + +std::wstring GetTime(LPCWSTR format) { + WCHAR buff[32]; + GetTimeFormat(LOCALE_SYSTEM_DEFAULT, 0, NULL, format, buff, 32); + return buff; +} + +Date GetDateJapan() { + SYSTEMTIME st_jst; + GetSystemTime(st_jst, 9); // JST is UTC+09 + return Date(st_jst.wYear, st_jst.wMonth, st_jst.wDay); +} + +std::wstring GetTimeJapan(LPCWSTR format) { + WCHAR buff[32]; + SYSTEMTIME st_jst; + GetSystemTime(st_jst, 9); // JST is UTC+09 + GetTimeFormat(LOCALE_SYSTEM_DEFAULT, 0, &st_jst, format, buff, 32); + return buff; +} + +std::wstring ToDateString(time_t seconds) { + time_t days, hours, minutes; + std::wstring date; + + if (seconds > 0) { + #define CALC_TIME(x, y) x = seconds / (y); seconds = seconds % (y); + CALC_TIME(days, 60 * 60 * 24); + CALC_TIME(hours, 60 * 60); + CALC_TIME(minutes, 60); + #undef CALC_TIME + date.clear(); + #define ADD_TIME(x, y) \ + if (x > 0) { \ + if (!date.empty()) date += L" "; \ + date += ToWstr(x) + y; \ + if (x > 1) date += L"s"; \ + } + ADD_TIME(days, L" day"); + ADD_TIME(hours, L" hour"); + ADD_TIME(minutes, L" minute"); + ADD_TIME(seconds, L" second"); + #undef ADD_TIME + } + + return date; +} + +unsigned int ToDayCount(const Date& date) { + return (date.year * 365) + (date.month * 30) + date.day; +} + +std::wstring ToTimeString(int seconds) { + int hours = seconds / 3600; + seconds = seconds % 3600; + int minutes = seconds / 60; + seconds = seconds % 60; + + #define TWO_DIGIT(x) (x >= 10 ? ToWstr(x) : L"0" + ToWstr(x)) + return (hours > 0 ? TWO_DIGIT(hours) + L":" : L"") + + TWO_DIGIT(minutes) + L":" + TWO_DIGIT(seconds); + #undef TWO_DIGIT +} + +const Date& EmptyDate() { + static const Date date; + return date; } \ No newline at end of file diff --git a/time.h b/src/base/time.h similarity index 56% rename from time.h rename to src/base/time.h index f727fccd5..49f205183 100644 --- a/time.h +++ b/src/base/time.h @@ -1,71 +1,65 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef TIME_H -#define TIME_H - -#include "std.h" -#include - -// ============================================================================= - -enum TimerId { - TIMER_MAIN = 1337, - TIMER_TAIGA = 74164 -}; - -class Date { - public: - Date(); - Date(const wstring& date); - Date(unsigned short year, unsigned short month, unsigned short day); - virtual ~Date() {} - - Date& operator = (const Date& date); - - bool operator == (const Date& date) const; - bool operator != (const Date& date) const; - bool operator < (const Date& date) const; - bool operator <= (const Date& date) const; - bool operator >= (const Date& date) const; - bool operator > (const Date& date) const; - - int operator - (const Date& date) const; - - operator bool() const; - operator SYSTEMTIME() const; - operator wstring() const; - - unsigned short year; - unsigned short month; - unsigned short day; -}; - -void GetSystemTime(SYSTEMTIME& st, int utc_offset = 0); - -Date GetDate(); -wstring GetTime(LPCWSTR lpFormat = L"HH':'mm':'ss"); - -Date GetDateJapan(); -wstring GetTimeJapan(LPCWSTR lpFormat = L"HH':'mm':'ss"); - -wstring ToDateString(time_t seconds); -unsigned int ToDayCount(const Date& date); -wstring ToTimeString(int seconds); - -#endif // TIME_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_TIME_H +#define TAIGA_BASE_TIME_H + +#include +#include +#include + +#include "comparable.h" + +class Date : public base::Comparable { +public: + Date(); + Date(const std::wstring& date); + Date(unsigned short year, unsigned short month, unsigned short day); + virtual ~Date() {} + + Date& operator = (const Date& date); + + int operator - (const Date& date) const; + + operator bool() const; + operator SYSTEMTIME() const; + operator std::wstring() const; + + unsigned short year; + unsigned short month; + unsigned short day; + +private: + base::CompareResult Compare(const Date& date) const; +}; + +void GetSystemTime(SYSTEMTIME& st, int utc_offset = 0); + +Date GetDate(); +std::wstring GetTime(LPCWSTR format = L"HH':'mm':'ss"); + +Date GetDateJapan(); +std::wstring GetTimeJapan(LPCWSTR format = L"HH':'mm':'ss"); + +std::wstring ToDateString(time_t seconds); +unsigned int ToDayCount(const Date& date); +std::wstring ToTimeString(int seconds); + +const Date& EmptyDate(); + +#endif // TAIGA_BASE_TIME_H \ No newline at end of file diff --git a/src/base/timer.cpp b/src/base/timer.cpp new file mode 100644 index 000000000..69d487f7a --- /dev/null +++ b/src/base/timer.cpp @@ -0,0 +1,131 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "timer.h" + +namespace base { + +Timer::Timer(unsigned int id, int interval, bool repeat) + : enabled_(true), + id_(id), + interval_(interval), + repeat_(repeat), + ticks_(interval) { +} + +bool Timer::enabled() const { + return enabled_; +} + +unsigned int Timer::id() const { + return id_; +} + +int Timer::interval() const { + return interval_; +} + +bool Timer::repeat() const { + return repeat_; +} + +int Timer::ticks() const { + return ticks_; +} + +void Timer::set_enabled(bool enabled) { + enabled_ = enabled; +} + +void Timer::set_id(unsigned int id) { + id_ = id; +} + +void Timer::set_interval(int interval) { + int interval_difference = interval - interval_; + interval_ = interval; + ticks_ += interval_difference; + + if (ticks_ < 1) + ticks_ = 1; // So that it reaches 0 on next tick +} + +void Timer::set_repeat(bool repeat) { + repeat_ = repeat; +} + +void Timer::set_ticks(int ticks) { + ticks_ = ticks; + + if (ticks_ > interval_) + ticks_ = interval_; +} + +void Timer::Reset() { + ticks_ = interval_; + enabled_ = true; +} + +void Timer::Tick() { + if (!enabled_) + return; + + if (interval_ > 0 && ticks_ > 0) + ticks_ -= 1; + + if (ticks_ == 0) { + OnTimeout(); + + if (repeat_) { + Reset(); + } else { + enabled_ = false; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +TimerManager::TimerManager() + : hwnd_(nullptr), id_(0) { +} + +TimerManager::~TimerManager() { + if (id_ != 0) + ::KillTimer(hwnd_, id_); +} + +Timer* TimerManager::timer(unsigned int id) { + if (timers_.find(id) != timers_.end()) + return timers_[id]; + + return nullptr; +} + +bool TimerManager::Initialize(HWND hwnd, TIMERPROC proc) { + hwnd_ = hwnd; + id_ = ::SetTimer(hwnd, 0, 1000 /*milliseconds*/, proc); + + return id_ != 0; +} + +void TimerManager::InsertTimer(const Timer* timer) { + timers_.insert(std::make_pair(timer->id(), const_cast(timer))); +} + +} // namespace base \ No newline at end of file diff --git a/src/base/timer.h b/src/base/timer.h new file mode 100644 index 000000000..a679eaff9 --- /dev/null +++ b/src/base/timer.h @@ -0,0 +1,82 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_TIMER_H +#define TAIGA_BASE_TIMER_H + +#include +#include + +namespace base { + +class Timer { +public: + Timer(unsigned int id, int interval, bool repeat); + virtual ~Timer() {} + + bool enabled() const; + unsigned int id() const; + int interval() const; + bool repeat() const; + int ticks() const; + + void set_enabled(bool enabled); + void set_id(unsigned int id); + void set_interval(int interval); + void set_repeat(bool repeat); + void set_ticks(int ticks); + + // Resets ticks to interval + void Reset(); + + // Counts down from the interval + void Tick(); + +protected: + // What to do when timer reaches zero + virtual void OnTimeout() = 0; + +private: + bool enabled_; + unsigned int id_; + int interval_; + bool repeat_; + int ticks_; +}; + +class TimerManager { +public: + TimerManager(); + virtual ~TimerManager(); + + Timer* timer(unsigned int id); + + virtual bool Initialize(HWND hwnd, TIMERPROC proc); + virtual void InsertTimer(const Timer* timer); + + virtual void OnTick() = 0; + +protected: + HWND hwnd_; + UINT_PTR id_; + std::map timers_; +}; + +} // namespace base + +#endif // TAIGA_BASE_TIMER_H \ No newline at end of file diff --git a/src/base/types.h b/src/base/types.h new file mode 100644 index 000000000..9731a9a0a --- /dev/null +++ b/src/base/types.h @@ -0,0 +1,58 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_TYPES_H +#define TAIGA_BASE_TYPES_H + +#include +#include +#include +#include + +namespace base { +namespace http { +class Request; +class Response; +} +} + +namespace base { + +// Unique ID type +typedef std::wstring uid_t; + +} // namespace base + +// Default enumeration type +typedef unsigned char enum_t; + +// Default string type +typedef std::wstring string_t; + +// Dictionary types +typedef std::map dictionary_t; +typedef std::map> multidictionary_t; + +// HTTP request and response +typedef base::http::Request HttpRequest; +typedef base::http::Response HttpResponse; + +// 64-bit integral data type (quadword) +typedef unsigned __int64 QWORD, *LPQWORD; + +#endif // TAIGA_BASE_TYPES_H \ No newline at end of file diff --git a/src/base/url.cpp b/src/base/url.cpp new file mode 100644 index 000000000..11dd9666a --- /dev/null +++ b/src/base/url.cpp @@ -0,0 +1,200 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include + +#include "foreach.h" +#include "string.h" +#include "url.h" + +Url::Url() + : protocol(base::http::kHttp), port(0) { +} + +Url::Url(const std::wstring& url) + : protocol(base::http::kHttp), port(0) { + Crack(url); +} + +//////////////////////////////////////////////////////////////////////////////// + +Url& Url::operator=(const Url& url) { + protocol = url.protocol; + host = url.host; + port = url.port; + path = url.path; + query = url.query; + fragment = url.fragment; + + return *this; +} + +void Url::operator=(const std::wstring& url) { + Crack(url); +} + +//////////////////////////////////////////////////////////////////////////////// + +void Url::Clear() { + protocol = base::http::kHttp; + host.clear(); + port = 0; + path.clear(); + query.clear(); + fragment.clear(); +} + +std::wstring Url::Build() const { + std::wstring url; + + switch (protocol) { + case base::http::kHttp: + default: + url += L"http"; + break; + case base::http::kHttps: + url += L"https"; + break; + } + url += L"://"; + + url += host; + + if (port > 0) + url += L":" + ToWstr(port); + + url += path; + + if (!query.empty()) { + std::wstring query_string; + foreach_(it, query) { + query_string += query_string.empty() ? L"?" : L"&"; + query_string += it->first + L"=" + EncodeUrl(it->second, false); + } + url += query_string; + } + + if (!fragment.empty()) + url += L"#" + fragment; + + return url; +} + +void Url::Crack(std::wstring url) { + Clear(); + + // Get protocol + size_t i = url.find(L"://", 0); + if (i != std::wstring::npos) { + std::wstring scheme = url.substr(0, i); + url = url.substr(i + 3); + if (IsEqual(scheme, L"https")) + protocol = base::http::kHttps; + } + + // Get host and path + i = url.find(L"/", 0); + if (i == std::wstring::npos) + i = url.length(); + host = url.substr(0, i); + path = url.substr(i); + + // Get port number + i = host.find(L":", 0); + if (i != std::wstring::npos) { + port = ToInt(host.substr(i + 1)); + host = host.substr(0, i); + } + + // Get fragment + i = path.find(L"#", 0); + if (i != std::wstring::npos) { + fragment = path.substr(i + 1); + path = path.substr(0, i); + } + + // Get query string + i = path.find(L"?", 0); + if (i != std::wstring::npos) { + std::wstring query_string = path.substr(i + 1); + path = path.substr(0, i); + std::vector parameters; + Split(query_string, L"&", parameters); + foreach_(it, parameters) { + i = it->find(L"=", 0); + if (i != std::wstring::npos) { + std::wstring name = it->substr(0, i); + std::wstring value = DecodeUrl(it->substr(i + 1)); + query.insert(std::make_pair(name, value)); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +std::wstring DecodeUrl(const std::wstring& input) { + std::string output; + output.reserve(input.size()); + + for (size_t i = 0; i < input.length(); i++) { + if (input[i] == L'%' && + i + 2 < input.length() && + IsHex(input[i + 1]) && IsHex(input[i + 2])) { + char c = 0; + static const wchar_t* digits = L"0123456789ABCDEF"; + for (size_t j = 0; j < 16; j++) { + if (input[i + 1] == digits[j]) + c += j << 4; + if (input[i + 2] == digits[j]) + c += j; + } + output.append(1, c); + i += 2; + } else { + output.append(1, static_cast(input[i])); + } + } + + return StrToWstr(output); +} + +std::wstring EncodeUrl(const std::wstring& input, bool encode_unreserved) { + std::string str = WstrToStr(input); + + std::wstring output; + output.reserve(input.size() * 2); + + for (size_t i = 0; i < str.length(); i++) { + if ((str[i] >= '0' && str[i] <= '9') || + (str[i] >= 'A' && str[i] <= 'Z') || + (str[i] >= 'a' && str[i] <= 'z') || + (!encode_unreserved && + (str[i] == '-' || str[i] == '.' || + str[i] == '_' || str[i] == '~'))) { + output.append(1, static_cast(str[i])); + } else { + static const wchar_t* digits = L"0123456789ABCDEF"; + output.append(L"%"); + output.append(&digits[(str[i] >> 4) & 0x0F], 1); + output.append(&digits[str[i] & 0x0F], 1); + } + } + + return output; +} \ No newline at end of file diff --git a/src/base/url.h b/src/base/url.h new file mode 100644 index 000000000..348c9990f --- /dev/null +++ b/src/base/url.h @@ -0,0 +1,61 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_URL_H +#define TAIGA_BASE_URL_H + +#include + +#include "map.h" + +typedef base::multimap query_t; + +namespace base { +namespace http { +enum Protocol { + kHttp, + kHttps +}; +} +} + +class Url { +public: + Url(); + Url(const std::wstring& url); + ~Url() {} + + void Clear(); + std::wstring Build() const; + void Crack(std::wstring url); + + Url& operator=(const Url& url); + void operator=(const std::wstring& url); + + base::http::Protocol protocol; + std::wstring host; + unsigned short port; + std::wstring path; + query_t query; + std::wstring fragment; +}; + +std::wstring DecodeUrl(const std::wstring& input); +std::wstring EncodeUrl(const std::wstring& input, bool encode_unreserved = false); + +#endif // TAIGA_BASE_URL_H \ No newline at end of file diff --git a/src/base/version.cpp b/src/base/version.cpp new file mode 100644 index 000000000..b291201b5 --- /dev/null +++ b/src/base/version.cpp @@ -0,0 +1,168 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include + +#include "string.h" +#include "version.h" + +namespace base { + +SemanticVersion::SemanticVersion() + : major(1), minor(0), patch(0) { +} + +SemanticVersion::SemanticVersion(const string_t& version) + : major(1), minor(0), patch(0) { + Parse(version); +} + +SemanticVersion::SemanticVersion(numeric_identifier_t major, + numeric_identifier_t minor, + numeric_identifier_t patch) + : major(major), minor(minor), patch(patch) { +} + +SemanticVersion& SemanticVersion::operator=(const SemanticVersion& version) { + major = version.major; + minor = version.minor; + patch = version.patch; + + prerelease_identifiers = version.prerelease_identifiers; + build_metadata = version.build_metadata; + + return *this; +} + +SemanticVersion::operator string_t() const { + string_t version = ToWstr(static_cast(major)) + L"." + + ToWstr(static_cast(minor)) + L"." + + ToWstr(static_cast(patch)); + if (!prerelease_identifiers.empty()) + version += L"-" + prerelease_identifiers; + if (!build_metadata.empty()) + version += L"+" + build_metadata; + + return version; +} + +//////////////////////////////////////////////////////////////////////////////// + +CompareResult SemanticVersion::Compare(const SemanticVersion& version) const { + if (major != version.major) + return major < version.major ? kLessThan : kGreaterThan; + if (minor != version.minor) + return minor < version.minor ? kLessThan : kGreaterThan; + if (patch != version.patch) + return patch < version.patch ? kLessThan : kGreaterThan; + + if (prerelease_identifiers != version.prerelease_identifiers) { + if (prerelease_identifiers.empty() && + !version.prerelease_identifiers.empty()) + return kGreaterThan; + if (!prerelease_identifiers.empty() && + version.prerelease_identifiers.empty()) + return kLessThan; + + std::vector identifiers_, identifiers; + Split(prerelease_identifiers, L".", identifiers_); + Split(version.prerelease_identifiers, L".", identifiers); + + size_t min_size = min(identifiers_.size(), identifiers.size()); + for (size_t i = 0; i < min_size; ++i) { + if (IsNumeric(identifiers_.at(i)) && IsNumeric(identifiers.at(i))) { + int lhs = ToInt(identifiers_.at(i)); + int rhs = ToInt(identifiers.at(i)); + if (lhs != rhs) + return lhs < rhs ? kLessThan : kGreaterThan; + } else { + int result = CompareStrings(identifiers_.at(i), identifiers.at(i)); + if (result != 0) + return result < 0 ? kLessThan : kGreaterThan; + } + } + + if (identifiers_.size() != identifiers.size()) + return identifiers_.size() < identifiers.size() ? + kLessThan : kGreaterThan; + } + + return kEqualTo; +} + +void SemanticVersion::Parse(const string_t& version) { + int identifier_index = kMajor; + size_t current_identifier_pos = 0; + + for (size_t i = 0; i < version.size(); ++i) { + switch (identifier_index) { + case kMajor: + case kMinor: + case kPatch: { + if (IsNumeric(version.at(i))) + continue; + string_t current_identifier = version.substr( + current_identifier_pos, i - current_identifier_pos); + switch (identifier_index) { + case kMajor: + major = ToInt(current_identifier); + break; + case kMinor: + minor = ToInt(current_identifier); + break; + case kPatch: + patch = ToInt(current_identifier); + break; + } + switch (version.at(i)) { + case L'.': + ++identifier_index; + break; + case L'-': + identifier_index = kPreRelease; + break; + case L'+': + identifier_index = kBuildMetadata; + break; + } + current_identifier_pos = i + 1; + break; + } + + case kPreRelease: { + if (version.at(i) != L'+' && i < version.size() - 1) + continue; + prerelease_identifiers = version.substr( + current_identifier_pos, i - current_identifier_pos); + identifier_index = kBuildMetadata; + current_identifier_pos = i + 1; + break; + } + + case kBuildMetadata: { + if (i < version.size() - 1) + continue; + build_metadata = version.substr( + current_identifier_pos, i - current_identifier_pos); + break; + } + } + } +} + +} // namespace base \ No newline at end of file diff --git a/src/base/version.h b/src/base/version.h new file mode 100644 index 000000000..c9cbdd600 --- /dev/null +++ b/src/base/version.h @@ -0,0 +1,70 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_VERSION_H +#define TAIGA_BASE_VERSION_H + +#include + +#include "comparable.h" + +namespace base { + +// An implementation of Semantic Versioning 2.0.0 that provides an easy way +// to compare version numbers. +// +// See http://semver.org for Semantic Versioning Specification. + +class SemanticVersion : public Comparable { +public: + typedef unsigned int numeric_identifier_t; + typedef std::wstring string_t; + + enum Version { + kMajor, + kMinor, + kPatch, + kPreRelease, + kBuildMetadata + }; + + SemanticVersion(); + SemanticVersion(const string_t& version); + SemanticVersion(numeric_identifier_t major, + numeric_identifier_t minor, + numeric_identifier_t patch); + ~SemanticVersion() {} + + SemanticVersion& operator = (const SemanticVersion& version); + + operator string_t() const; + + numeric_identifier_t major; + numeric_identifier_t minor; + numeric_identifier_t patch; + string_t prerelease_identifiers; + string_t build_metadata; + +private: + CompareResult Compare(const SemanticVersion& version) const; + void Parse(const string_t& version); +}; + +} // namespace base + +#endif // TAIGA_BASE_VERSION_H \ No newline at end of file diff --git a/src/base/xml.cpp b/src/base/xml.cpp new file mode 100644 index 000000000..3b790406b --- /dev/null +++ b/src/base/xml.cpp @@ -0,0 +1,83 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "file.h" +#include "foreach.h" +#include "string.h" +#include "xml.h" + +struct xml_string_writer: pugi::xml_writer { + std::string result; + + virtual void write(const void* data, size_t size) { + result += std::string(static_cast(data), size); + } +}; + +std::wstring XmlGetNodeAsString(pugi::xml_node node) { + xml_string_writer writer; + node.print(writer); + + return StrToWstr(writer.result); +} + +int XmlReadIntValue(pugi::xml_node& node, const wchar_t* name) { + return _wtoi(node.child_value(name)); +} + +std::wstring XmlReadStrValue(pugi::xml_node& node, const wchar_t* name) { + return node.child_value(name); +} + +void XmlReadChildNodes(pugi::xml_node& parent_node, + std::vector& output, + const wchar_t* name) { + foreach_xmlnode_(child_node, parent_node, name) { + output.push_back(child_node.child_value()); + } +} + +void XmlWriteChildNodes(pugi::xml_node& parent_node, + const std::vector& input, + const wchar_t* name, + pugi::xml_node_type node_type) { + foreach_(it, input) { + xml_node child_node = parent_node.append_child(name); + child_node.append_child(node_type).set_value(it->c_str()); + } +} + +void XmlWriteIntValue(pugi::xml_node& node, const wchar_t* name, int value) { + xml_node child = node.append_child(name); + child.append_child(pugi::node_pcdata).set_value(ToWstr(value).c_str()); +} + +void XmlWriteStrValue(pugi::xml_node& node, const wchar_t* name, + const wchar_t* value, pugi::xml_node_type node_type) { + xml_node child = node.append_child(name); + child.append_child(node_type).set_value(value); +} + +bool XmlWriteDocumentToFile(const pugi::xml_document& document, + const std::wstring& path) { + CreateFolder(GetPathOnly(path)); + + const pugi::char_t* indent = L"\x09"; // horizontal tab + unsigned int flags = pugi::format_default | pugi::format_write_bom; + return document.save_file(path.c_str(), indent, flags); +} \ No newline at end of file diff --git a/src/base/xml.h b/src/base/xml.h new file mode 100644 index 000000000..9844ce868 --- /dev/null +++ b/src/base/xml.h @@ -0,0 +1,56 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_BASE_XML_H +#define TAIGA_BASE_XML_H + +#include +#include + +#include + +using pugi::xml_document; +using pugi::xml_node; +using pugi::xml_parse_result; + +#define foreach_xmlnode_(node, parent, name) \ + for (pugi::xml_node node = parent.child(name); node; \ + node = node.next_sibling(name)) + +std::wstring XmlGetNodeAsString(pugi::xml_node node); + +int XmlReadIntValue(pugi::xml_node& node, const wchar_t* name); +std::wstring XmlReadStrValue(pugi::xml_node& node, const wchar_t* name); + +void XmlReadChildNodes(pugi::xml_node& parent_node, + std::vector& output, + const wchar_t* name); +void XmlWriteChildNodes(pugi::xml_node& parent_node, + const std::vector& input, + const wchar_t* name, + pugi::xml_node_type node_type = pugi::node_pcdata); + +void XmlWriteIntValue(pugi::xml_node& node, const wchar_t* name, int value); +void XmlWriteStrValue(pugi::xml_node& node, const wchar_t* name, + const wchar_t* value, + pugi::xml_node_type node_type = pugi::node_pcdata); + +bool XmlWriteDocumentToFile(const pugi::xml_document& document, + const std::wstring& path); + +#endif // TAIGA_BASE_XML_H \ No newline at end of file diff --git a/src/library/anime.cpp b/src/library/anime.cpp new file mode 100644 index 000000000..3d7367057 --- /dev/null +++ b/src/library/anime.cpp @@ -0,0 +1,37 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "anime.h" + +namespace anime { + +MyInformation::MyInformation() + : watched_episodes(0), + score(0), + status(kNotInList), + rewatching(FALSE), + rewatching_ep(0) { +} + +LocalInformation::LocalInformation() + : last_aired_episode(0), + playing(false), + use_alternative(false) { +} + +} // namespace anime \ No newline at end of file diff --git a/src/library/anime.h b/src/library/anime.h new file mode 100644 index 000000000..33c7b814c --- /dev/null +++ b/src/library/anime.h @@ -0,0 +1,118 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_LIBRARY_ANIME_H +#define TAIGA_LIBRARY_ANIME_H + +#include +#include + +#include "base/time.h" + +namespace anime { + +// ID_NOTINLIST +// Used in Episode data to denote the item is not in user's list. +// Item may or may not be in the database. +// +// ID_UNKNOWN +// There's no item in the database with this ID. +// This is the default ID for all anime items. +// +enum AnimeId { + ID_NOTINLIST = -1, + ID_UNKNOWN = 0 +}; + +enum SeriesStatus { + kUnknownStatus, + kFinishedAiring, + kAiring, + kNotYetAired +}; + +enum SeriesType { + kUnknownType, + kTv, + kOva, + kMovie, + kSpecial, + kOna, + kMusic +}; + +enum SeriesEpisodeInfo { + kUnknownEpisodeCount = -1, + kUnknownEpisodeLength = -1 +}; + +enum MyStatus { + kMyStatusFirst = 1, + kNotInList = 0, + kWatching, + kCompleted, + kOnHold, + kDropped, + kPlanToWatch, + kMyStatusLast +}; + +enum AgeRating { + kUnknownAgeRating, + kAgeRatingG, + kAgeRatingPG, + kAgeRatingPG13, + kAgeRatingR17, + kAgeRatingR18 +}; + +// Invalid for anime items that are not in user's list +class MyInformation { + public: + MyInformation(); + virtual ~MyInformation() {} + + int watched_episodes; + int score; + int status; + int rewatching; + int rewatching_ep; + Date date_start; + Date date_finish; + std::wstring last_updated; + std::wstring tags; +}; + +// For all kinds of other temporary information +class LocalInformation { + public: + LocalInformation(); + virtual ~LocalInformation() {} + + int last_aired_episode; + std::vector available_episodes; + std::wstring next_episode_path; + std::wstring folder; + std::vector synonyms; + bool playing; + bool use_alternative; +}; + +} // namespace anime + +#endif // TAIGA_LIBRARY_ANIME_H \ No newline at end of file diff --git a/src/library/anime_db.cpp b/src/library/anime_db.cpp new file mode 100644 index 000000000..471d9ff68 --- /dev/null +++ b/src/library/anime_db.cpp @@ -0,0 +1,648 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/file.h" +#include "base/foreach.h" +#include "base/log.h" +#include "base/string.h" +#include "base/version.h" +#include "base/xml.h" +#include "library/anime.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "library/history.h" +#include "sync/manager.h" +#include "sync/myanimelist_util.h" +#include "sync/service.h" +#include "taiga/http.h" +#include "taiga/path.h" +#include "taiga/settings.h" +#include "track/recognition.h" +#include "ui/dlg/dlg_anime_list.h" +#include "ui/ui.h" + +anime::Database AnimeDatabase; + +namespace anime { + +bool Database::LoadDatabase() { + xml_document document; + std::wstring path = taiga::GetPath(taiga::kPathDatabaseAnime); + unsigned int options = pugi::parse_default & ~pugi::parse_eol; + xml_parse_result parse_result = document.load_file(path.c_str(), options); + + if (parse_result.status != pugi::status_ok) + return false; + + xml_node meta_node = document.child(L"meta"); + std::wstring meta_version = XmlReadStrValue(meta_node, L"version"); + + if (!meta_version.empty()) { + xml_node database_node = document.child(L"database"); + ReadDatabaseNode(database_node); + } else { + LOG(LevelWarning, L"Reading database in compatibility mode"); + ReadDatabaseInCompatibilityMode(document); + } + + return true; +} + +void Database::ReadDatabaseNode(xml_node& database_node) { + foreach_xmlnode_(node, database_node, L"anime") { + std::map id_map; + + foreach_xmlnode_(id_node, node, L"id") { + std::wstring id = id_node.child_value(); + std::wstring name = id_node.attribute(L"name").as_string(); + enum_t service_id = ServiceManager.GetServiceIdByName(name); + id_map[service_id] = id; + } + + Item& item = items[ToInt(id_map[sync::kTaiga])]; // Creates the item if it doesn't exist + + foreach_(it, id_map) + item.SetId(it->second, it->first); + + enum_t source = sync::kTaiga; + auto service = ServiceManager.service(XmlReadStrValue(node, L"source")); + if (service) + source = service->id(); + + std::vector synonyms; + XmlReadChildNodes(node, synonyms, L"synonym"); + + item.SetSource(source); + item.SetSlug(XmlReadStrValue(node, L"slug")); + + item.SetTitle(XmlReadStrValue(node, L"title")); + item.SetEnglishTitle(XmlReadStrValue(node, L"english")); + item.SetSynonyms(synonyms); + item.SetType(XmlReadIntValue(node, L"type")); + item.SetAiringStatus(XmlReadIntValue(node, L"status")); + item.SetEpisodeCount(XmlReadIntValue(node, L"episode_count")); + item.SetEpisodeLength(XmlReadIntValue(node, L"episode_length")); + item.SetDateStart(Date(XmlReadStrValue(node, L"date_start"))); + item.SetDateEnd(Date(XmlReadStrValue(node, L"date_end"))); + item.SetImageUrl(XmlReadStrValue(node, L"image")); + item.SetAgeRating(XmlReadIntValue(node, L"age_rating")); + item.SetGenres(XmlReadStrValue(node, L"genres")); + item.SetProducers(XmlReadStrValue(node, L"producers")); + item.SetScore(XmlReadStrValue(node, L"score")); + item.SetPopularity(XmlReadStrValue(node, L"popularity")); + item.SetSynopsis(XmlReadStrValue(node, L"synopsis")); + item.SetLastModified(_wtoi64(XmlReadStrValue(node, L"modified").c_str())); + } +} + +bool Database::SaveDatabase() { + if (items.empty()) + return false; + + xml_document document; + + xml_node meta_node = document.append_child(L"meta"); + XmlWriteStrValue(meta_node, L"version", L"1.1"); + + xml_node database_node = document.append_child(L"database"); + WriteDatabaseNode(database_node); + + std::wstring path = taiga::GetPath(taiga::kPathDatabaseAnime); + return XmlWriteDocumentToFile(document, path); +} + +void Database::WriteDatabaseNode(xml_node& database_node) { + foreach_(it, items) { + xml_node anime_node = database_node.append_child(L"anime"); + + for (int i = 0; i <= sync::kLastService; i++) { + std::wstring id = it->second.GetId(i); + if (!id.empty()) { + xml_node child = anime_node.append_child(L"id"); + std::wstring name = ServiceManager.GetServiceNameById(static_cast(i)); + child.append_attribute(L"name") = name.c_str(); + child.append_child(pugi::node_pcdata).set_value(id.c_str()); + } + } + + std::wstring source = ServiceManager.GetServiceNameById( + static_cast(it->second.GetSource())); + + #define XML_WC(n, v, t) \ + if (!v.empty()) XmlWriteChildNodes(anime_node, v, n, t) + #define XML_WD(n, v) \ + if (v) XmlWriteStrValue(anime_node, n, std::wstring(v).c_str()) + #define XML_WI(n, v) \ + if (v > 0) XmlWriteIntValue(anime_node, n, v) + #define XML_WS(n, v, t) \ + if (!v.empty()) XmlWriteStrValue(anime_node, n, v.c_str(), t) + XML_WS(L"source", source, pugi::node_pcdata); + XML_WS(L"slug", it->second.GetSlug(), pugi::node_pcdata); + XML_WS(L"title", it->second.GetTitle(), pugi::node_cdata); + XML_WS(L"english", it->second.GetEnglishTitle(), pugi::node_cdata); + XML_WC(L"synonym", it->second.GetSynonyms(), pugi::node_cdata); + XML_WI(L"type", it->second.GetType()); + XML_WI(L"status", it->second.GetAiringStatus()); + XML_WI(L"episode_count", it->second.GetEpisodeCount()); + XML_WI(L"episode_length", it->second.GetEpisodeLength()); + XML_WD(L"date_start", it->second.GetDateStart()); + XML_WD(L"date_end", it->second.GetDateEnd()); + XML_WS(L"image", it->second.GetImageUrl(), pugi::node_pcdata); + XML_WI(L"age_rating", it->second.GetAgeRating()); + XML_WS(L"genres", Join(it->second.GetGenres(), L", "), pugi::node_pcdata); + XML_WS(L"producers", Join(it->second.GetProducers(), L", "), pugi::node_pcdata); + XML_WS(L"score", it->second.GetScore(), pugi::node_pcdata); + XML_WS(L"popularity", it->second.GetPopularity(), pugi::node_pcdata); + XML_WS(L"synopsis", it->second.GetSynopsis(), pugi::node_cdata); + XML_WS(L"modified", ToWstr(it->second.GetLastModified()), pugi::node_pcdata); + #undef XML_WS + #undef XML_WI + #undef XML_WD + #undef XML_WC + } +} + +//////////////////////////////////////////////////////////////////////////////// + +Item* Database::FindItem(int id) { + if (id > ID_UNKNOWN) { + auto it = items.find(id); + if (it != items.end()) + return &it->second; + } + + return nullptr; +} + +Item* Database::FindItem(const std::wstring& id, enum_t service) { + if (!id.empty()) + foreach_(it, items) + if (id == it->second.GetId(service)) + return &it->second; + + return nullptr; +} + +Item* Database::FindSequel(int anime_id) { + if (taiga::GetCurrentServiceId() != sync::kMyAnimeList) + return nullptr; + + int sequel_id = ID_UNKNOWN; + + switch (anime_id) { + #define SEQUEL_ID(p, s) case p: sequel_id = s; break; + // Gintama -> Gintama' + SEQUEL_ID(918, 9969); + // Tegami Bachi -> Tegami Bachi Reverse + SEQUEL_ID(6444, 8311); + // Fate/Zero -> Fate/Zero 2nd Season + SEQUEL_ID(10087, 11741); + // Towa no Qwon + SEQUEL_ID(10294, 10713); + SEQUEL_ID(10713, 10714); + SEQUEL_ID(10714, 10715); + SEQUEL_ID(10715, 10716); + SEQUEL_ID(10716, 10717); + #undef SEQUEL_ID + } + + return FindItem(sequel_id); +} + +//////////////////////////////////////////////////////////////////////////////// + +void Database::ClearInvalidItems() { + for (auto it = items.begin(); it != items.end(); ) { + if (!it->second.GetId() || it->first != it->second.GetId()) { + LOG(LevelDebug, L"ID: " + ToWstr(it->first)); + items.erase(it++); + } else { + ++it; + } + } +} + +int Database::UpdateItem(const Item& new_item) { + Item* item = nullptr; + + for (enum_t i = sync::kTaiga; i <= sync::kLastService; i++) { + item = FindItem(new_item.GetId(i), i); + if (item) + break; + } + + if (!item) { + /* + auto service = ServiceManager.service(Settings[taiga::kSync_ActiveService]); + if (new_item.GetSource() != service->id()) { + // TODO: Try to find the same item by similarity + } + */ + + auto source = new_item.GetSource(); + + bool id_is_numeric = false; + switch (source) { + case sync::kMyAnimeList: + case sync::kHummingbird: + id_is_numeric = true; + break; + } + + int id = 1; + + if (id_is_numeric) { + id = ToInt(new_item.GetId(source)); + } else { + // Generate a new ID + while (FindItem(id)) + ++id; + } + + // Add a new item + item = &items[id]; + item->SetId(ToWstr(id), sync::kTaiga); + } + + // Update series information if new information is, well, new. + if (!item->GetLastModified() || + new_item.GetLastModified() >= item->GetLastModified()) { + item->SetLastModified(new_item.GetLastModified()); + + for (enum_t i = sync::kFirstService; i <= sync::kLastService; i++) + if (!new_item.GetId(i).empty()) + item->SetId(new_item.GetId(i), i); + + if (new_item.GetSource() != sync::kTaiga) + item->SetSource(new_item.GetSource()); + + if (new_item.GetType() != kUnknownType) + item->SetType(new_item.GetType()); + if (new_item.GetEpisodeCount() != kUnknownEpisodeCount) + item->SetEpisodeCount(new_item.GetEpisodeCount()); + if (new_item.GetEpisodeLength() != kUnknownEpisodeLength) + item->SetEpisodeLength(new_item.GetEpisodeLength()); + if (new_item.GetAiringStatus(false) != kUnknownStatus) + item->SetAiringStatus(new_item.GetAiringStatus()); + if (!new_item.GetSlug().empty()) + item->SetSlug(new_item.GetSlug()); + if (!new_item.GetTitle().empty()) + item->SetTitle(new_item.GetTitle()); + if (!new_item.GetEnglishTitle(false).empty()) + item->SetEnglishTitle(new_item.GetEnglishTitle()); + if (!new_item.GetSynonyms().empty()) + item->SetSynonyms(new_item.GetSynonyms()); + if (IsValidDate(new_item.GetDateStart())) + item->SetDateStart(new_item.GetDateStart()); + if (IsValidDate(new_item.GetDateEnd())) + item->SetDateEnd(new_item.GetDateEnd()); + if (!new_item.GetImageUrl().empty()) + item->SetImageUrl(new_item.GetImageUrl()); + if (new_item.GetAgeRating() != kUnknownAgeRating) + item->SetAgeRating(new_item.GetAgeRating()); + if (!new_item.GetGenres().empty()) + item->SetGenres(new_item.GetGenres()); + if (!new_item.GetPopularity().empty()) + item->SetPopularity(new_item.GetPopularity()); + if (!new_item.GetProducers().empty()) + item->SetProducers(new_item.GetProducers()); + if (!new_item.GetScore().empty()) + item->SetScore(new_item.GetScore()); + if (!new_item.GetSynopsis().empty()) + item->SetSynopsis(new_item.GetSynopsis()); + + // Update clean titles, if necessary + if (!new_item.GetTitle().empty() || + !new_item.GetSynonyms().empty() || + !new_item.GetEnglishTitle(false).empty()) + Meow.UpdateCleanTitles(item->GetId()); + } + + // Update user information + if (new_item.IsInList()) { + // Make sure our pointer to MyInformation class is valid + item->AddtoUserList(); + + item->SetMyLastWatchedEpisode(new_item.GetMyLastWatchedEpisode(false)); + item->SetMyScore(new_item.GetMyScore(false)); + item->SetMyStatus(new_item.GetMyStatus(false)); + item->SetMyRewatching(new_item.GetMyRewatching(false)); + item->SetMyRewatchingEp(new_item.GetMyRewatchingEp()); + item->SetMyDateStart(new_item.GetMyDateStart()); + item->SetMyDateEnd(new_item.GetMyDateEnd()); + item->SetMyLastUpdated(new_item.GetMyLastUpdated()); + item->SetMyTags(new_item.GetMyTags(false)); + } + + return item->GetId(); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +bool Database::LoadList() { + ClearUserData(); + + if (taiga::GetCurrentUsername().empty()) + return false; + + xml_document document; + std::wstring path = taiga::GetPath(taiga::kPathUserLibrary); + xml_parse_result parse_result = document.load_file(path.c_str()); + + if (parse_result.status != pugi::status_ok) { + if (parse_result.status == pugi::status_file_not_found) { + return CheckOldUserDirectory(); + } else { + MessageBox(nullptr, L"Could not read anime list.", path.c_str(), + MB_OK | MB_ICONERROR); + return false; + } + } + + xml_node meta_node = document.child(L"meta"); + std::wstring meta_version = XmlReadStrValue(meta_node, L"version"); + + if (!meta_version.empty()) { + xml_node node_database = document.child(L"database"); + ReadDatabaseNode(node_database); + + xml_node node_library = document.child(L"library"); + foreach_xmlnode_(node, node_library, L"anime") { + Item anime_item; + anime_item.SetId(XmlReadStrValue(node, L"id"), sync::kTaiga); + + anime_item.AddtoUserList(); + anime_item.SetMyLastWatchedEpisode(XmlReadIntValue(node, L"progress")); + anime_item.SetMyDateStart(XmlReadStrValue(node, L"date_start")); + anime_item.SetMyDateEnd(XmlReadStrValue(node, L"date_end")); + anime_item.SetMyScore(XmlReadIntValue(node, L"score")); + anime_item.SetMyStatus(XmlReadIntValue(node, L"status")); + anime_item.SetMyRewatching(XmlReadIntValue(node, L"rewatching")); + anime_item.SetMyRewatchingEp(XmlReadIntValue(node, L"rewatching_ep")); + anime_item.SetMyTags(XmlReadStrValue(node, L"tags")); + anime_item.SetMyLastUpdated(XmlReadStrValue(node, L"last_updated")); + + UpdateItem(anime_item); + } + + } else { + LOG(LevelWarning, L"Reading list in compatibility mode"); + ReadListInCompatibilityMode(document); + } + + return true; +} + +bool Database::SaveList(bool include_database) { + if (items.empty()) + return false; + + xml_document document; + + xml_node meta_node = document.append_child(L"meta"); + XmlWriteStrValue(meta_node, L"version", L"1.1"); + + if (include_database) { + xml_node node_database = document.append_child(L"database"); + WriteDatabaseNode(node_database); + } + + xml_node node_library = document.append_child(L"library"); + + foreach_(it, items) { + Item* item = &it->second; + if (item->IsInList()) { + xml_node node = node_library.append_child(L"anime"); + XmlWriteIntValue(node, L"id", item->GetId()); + XmlWriteIntValue(node, L"progress", item->GetMyLastWatchedEpisode(false)); + XmlWriteStrValue(node, L"date_start", std::wstring(item->GetMyDateStart()).c_str()); + XmlWriteStrValue(node, L"date_end", std::wstring(item->GetMyDateEnd()).c_str()); + XmlWriteIntValue(node, L"score", item->GetMyScore(false)); + XmlWriteIntValue(node, L"status", item->GetMyStatus(false)); + XmlWriteIntValue(node, L"rewatching", item->GetMyRewatching(false)); + XmlWriteIntValue(node, L"rewatching_ep", item->GetMyRewatchingEp()); + XmlWriteStrValue(node, L"tags", item->GetMyTags(false).c_str()); + XmlWriteStrValue(node, L"last_updated", item->GetMyLastUpdated().c_str()); + } + } + + std::wstring path = taiga::GetPath(taiga::kPathUserLibrary); + return XmlWriteDocumentToFile(document, path); +} + +//////////////////////////////////////////////////////////////////////////////// + +int Database::GetItemCount(int status, bool check_history) { + int count = 0; + + // Get current count + foreach_(it, items) + if (it->second.GetMyStatus(false) == status) + count++; + + // Search queued items for status changes + if (check_history) { + foreach_(it, History.queue.items) { + if (it->status) { + if (status == *it->status) { + count++; + } else { + auto anime_item = FindItem(it->anime_id); + if (anime_item && status == anime_item->GetMyStatus(false)) + count--; + } + } + } + } + + return count; +} + +//////////////////////////////////////////////////////////////////////////////// + +void Database::AddToList(int anime_id, int status) { + auto anime_item = FindItem(anime_id); + + if (!anime_item) + return; + + anime_item->AddtoUserList(); + + HistoryItem history_item; + history_item.anime_id = anime_id; + history_item.status = status; + if (status == anime::kCompleted) { + history_item.episode = anime_item->GetEpisodeCount(); + history_item.date_finish = GetDate(); + } + history_item.mode = taiga::kHttpServiceAddLibraryEntry; + History.queue.Add(history_item); + + SaveList(); + + ui::OnLibraryEntryAdd(anime_id); +} + +void Database::ClearUserData() { + ui::DlgAnimeList.SetCurrentId(ID_UNKNOWN); + + foreach_(it, items) + it->second.RemoveFromUserList(); +} + +bool Database::DeleteListItem(int anime_id) { + auto anime_item = FindItem(anime_id); + + if (!anime_item) + return false; + if (!anime_item->IsInList()) + return false; + + anime_item->RemoveFromUserList(); + + ui::ChangeStatusText(L"Item deleted. (" + anime_item->GetTitle() + L")"); + ui::OnLibraryEntryDelete(anime_item->GetId()); + + return true; +} + +void Database::UpdateItem(const HistoryItem& history_item) { + auto anime_item = FindItem(history_item.anime_id); + + if (!anime_item) + return; + + // Edit episode + if (history_item.episode) { + anime_item->SetMyLastWatchedEpisode(*history_item.episode); + } + // Edit score + if (history_item.score) { + anime_item->SetMyScore(*history_item.score); + } + // Edit status + if (history_item.status) { + anime_item->SetMyStatus(*history_item.status); + } + // Edit re-watching status + if (history_item.enable_rewatching) { + anime_item->SetMyRewatching(*history_item.enable_rewatching); + } + // Edit tags + if (history_item.tags) { + anime_item->SetMyTags(*history_item.tags); + } + // Edit dates + if (history_item.date_start) { + anime_item->SetMyDateStart(*history_item.date_start); + } + if (history_item.date_finish) { + anime_item->SetMyDateEnd(*history_item.date_finish); + } + // Delete + if (history_item.mode == taiga::kHttpServiceDeleteLibraryEntry) { + DeleteListItem(anime_item->GetId()); + } + + SaveList(); + + History.queue.Remove(); + History.queue.Check(false); + + ui::OnLibraryEntryChange(history_item.anime_id); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +bool Database::CheckOldUserDirectory() { + std::wstring path = taiga::GetPath(taiga::kPathUser) + + taiga::GetCurrentUsername(); + + if (FolderExists(path)) { + LOG(LevelWarning, L"Moving old user directory to its new place"); + auto service_name = ServiceManager.GetServiceNameById(sync::kMyAnimeList); + std::wstring new_path = path + L"@" + service_name; + if (MoveFileEx(path.c_str(), new_path.c_str(), 0) != 0) { + return LoadList(); + } + } + + return true; +} + +void Database::ReadDatabaseInCompatibilityMode(xml_document& document) { + xml_node animedb_node = document.child(L"animedb"); + + foreach_xmlnode_(node, animedb_node, L"anime") { + std::wstring id = XmlReadStrValue(node, L"series_animedb_id"); + Item& item = items[ToInt(id)]; // Creates the item if it doesn't exist + item.SetId(id, sync::kTaiga); + item.SetId(id, sync::kMyAnimeList); + item.SetTitle(XmlReadStrValue(node, L"series_title")); + item.SetEnglishTitle(XmlReadStrValue(node, L"series_english")); + item.SetSynonyms(XmlReadStrValue(node, L"series_synonyms")); + item.SetType(sync::myanimelist::TranslateSeriesTypeFrom(XmlReadIntValue(node, L"series_type"))); + item.SetEpisodeCount(XmlReadIntValue(node, L"series_episodes")); + item.SetAiringStatus(sync::myanimelist::TranslateSeriesStatusFrom(XmlReadIntValue(node, L"series_status"))); + item.SetDateStart(Date(XmlReadStrValue(node, L"series_start"))); + item.SetDateEnd(Date(XmlReadStrValue(node, L"series_end"))); + item.SetImageUrl(XmlReadStrValue(node, L"series_image")); + item.SetGenres(XmlReadStrValue(node, L"genres")); + item.SetProducers(XmlReadStrValue(node, L"producers")); + item.SetScore(XmlReadStrValue(node, L"score")); + item.SetPopularity(XmlReadStrValue(node, L"popularity")); + item.SetSynopsis(XmlReadStrValue(node, L"synopsis")); + item.SetLastModified(_wtoi64(XmlReadStrValue(node, L"last_modified").c_str())); + } +} + +void Database::ReadListInCompatibilityMode(xml_document& document) { + xml_node myanimelist = document.child(L"myanimelist"); + + foreach_xmlnode_(node, myanimelist, L"anime") { + Item anime_item; + anime_item.SetId(XmlReadStrValue(node, L"series_animedb_id"), sync::kMyAnimeList); + + anime_item.SetTitle(XmlReadStrValue(node, L"series_title")); + anime_item.SetSynonyms(XmlReadStrValue(node, L"series_synonyms")); + anime_item.SetType(sync::myanimelist::TranslateSeriesTypeFrom(XmlReadIntValue(node, L"series_type"))); + anime_item.SetEpisodeCount(XmlReadIntValue(node, L"series_episodes")); + anime_item.SetAiringStatus(sync::myanimelist::TranslateSeriesStatusFrom(XmlReadIntValue(node, L"series_status"))); + anime_item.SetDateStart(XmlReadStrValue(node, L"series_start")); + anime_item.SetDateEnd(XmlReadStrValue(node, L"series_end")); + anime_item.SetImageUrl(XmlReadStrValue(node, L"series_image")); + anime_item.SetLastModified(0); + + anime_item.AddtoUserList(); + anime_item.SetMyLastWatchedEpisode(XmlReadIntValue(node, L"my_watched_episodes")); + anime_item.SetMyDateStart(XmlReadStrValue(node, L"my_start_date")); + anime_item.SetMyDateEnd(XmlReadStrValue(node, L"my_finish_date")); + anime_item.SetMyScore(XmlReadIntValue(node, L"my_score")); + anime_item.SetMyStatus(sync::myanimelist::TranslateMyStatusFrom(XmlReadIntValue(node, L"my_status"))); + anime_item.SetMyRewatching(XmlReadIntValue(node, L"my_rewatching")); + anime_item.SetMyRewatchingEp(XmlReadIntValue(node, L"my_rewatching_ep")); + anime_item.SetMyLastUpdated(XmlReadStrValue(node, L"my_last_updated")); + anime_item.SetMyTags(XmlReadStrValue(node, L"my_tags")); + + UpdateItem(anime_item); + } +} + +} // namespace anime \ No newline at end of file diff --git a/src/library/anime_db.h b/src/library/anime_db.h new file mode 100644 index 000000000..cb9f293e6 --- /dev/null +++ b/src/library/anime_db.h @@ -0,0 +1,73 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_LIBRARY_ANIME_DB_H +#define TAIGA_LIBRARY_ANIME_DB_H + +#include + +#include "library/anime_item.h" + +class HistoryItem; +namespace pugi { +class xml_document; +class xml_node; +} + +namespace anime { + +class Database { +public: + bool LoadDatabase(); + bool SaveDatabase(); + + Item* FindItem(int id); + Item* FindItem(const std::wstring& id, enum_t service); + Item* FindSequel(int anime_id); + + void ClearInvalidItems(); + int UpdateItem(const Item& item); + +public: + bool LoadList(); + bool SaveList(bool include_database = false); + + int GetItemCount(int status, bool check_history = true); + + void AddToList(int anime_id, int status); + void ClearUserData(); + bool DeleteListItem(int anime_id); + void UpdateItem(const HistoryItem& history_item); + +public: + std::map items; + +private: + void ReadDatabaseNode(pugi::xml_node& database_node); + void WriteDatabaseNode(pugi::xml_node& database_node); + + bool CheckOldUserDirectory(); + void ReadDatabaseInCompatibilityMode(pugi::xml_document& document); + void ReadListInCompatibilityMode(pugi::xml_document& document); +}; + +} // namespace anime + +extern anime::Database AnimeDatabase; + +#endif // TAIGA_LIBRARY_ANIME_DB_H \ No newline at end of file diff --git a/anime_episode.cpp b/src/library/anime_episode.cpp similarity index 75% rename from anime_episode.cpp rename to src/library/anime_episode.cpp index 674f7d5b5..7ddc00f34 100644 --- a/anime_episode.cpp +++ b/src/library/anime_episode.cpp @@ -1,63 +1,58 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "anime.h" -#include "anime_db.h" -#include "anime_episode.h" - -#include "common.h" - -anime::Episode CurrentEpisode; - -namespace anime { - -// ============================================================================= - -Episode::Episode() - : anime_id(ID_UNKNOWN), processed(false) { -} - -void Episode::Clear() { - anime_id = ID_UNKNOWN; - audio_type.clear(); - checksum.clear(); - extras.clear(); - file.clear(); - folder.clear(); - format.clear(); - group.clear(); - name.clear(); - number.clear(); - resolution.clear(); - title.clear(); - clean_title.clear(); - version.clear(); - video_type.clear(); - year.clear(); - processed = false; -} - -void Episode::Set(int anime_id) { - this->anime_id = anime_id; - this->processed = false; - UpdateAllMenus(AnimeDatabase.FindItem(anime_id)); -} - -} // namespace anime \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "library/anime.h" +#include "library/anime_db.h" +#include "library/anime_episode.h" +#include "ui/menu.h" + +anime::Episode CurrentEpisode; + +namespace anime { + +Episode::Episode() + : anime_id(ID_UNKNOWN), processed(false) { +} + +void Episode::Clear() { + anime_id = ID_UNKNOWN; + audio_type.clear(); + checksum.clear(); + extras.clear(); + file.clear(); + folder.clear(); + format.clear(); + group.clear(); + name.clear(); + number.clear(); + resolution.clear(); + title.clear(); + clean_title.clear(); + version.clear(); + video_type.clear(); + year.clear(); + processed = false; +} + +void Episode::Set(int anime_id) { + this->anime_id = anime_id; + this->processed = false; + ui::Menus.UpdateAll(AnimeDatabase.FindItem(anime_id)); +} + +} // namespace anime \ No newline at end of file diff --git a/anime_episode.h b/src/library/anime_episode.h similarity index 58% rename from anime_episode.h rename to src/library/anime_episode.h index e01bd459a..53373ee00 100644 --- a/anime_episode.h +++ b/src/library/anime_episode.h @@ -1,59 +1,57 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef ANIME_EPISODE_H -#define ANIME_EPISODE_H - -#include "std.h" - -namespace anime { - -// ============================================================================= - -class Episode { - public: - Episode(); - virtual ~Episode() {} - - void Clear(); - void Set(int anime_id); - - int anime_id; - wstring file; - wstring folder; - wstring format; - wstring title; - wstring clean_title; - wstring name; - wstring group; - wstring number; - wstring version; - wstring resolution; - wstring audio_type; - wstring video_type; - wstring checksum; - wstring extras; - wstring year; - bool processed; -}; - -} // namespace anime - -extern anime::Episode CurrentEpisode; - -#endif // ANIME_EPISODE_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_LIBRARY_ANIME_EPISODE_H +#define TAIGA_LIBRARY_ANIME_EPISODE_H + +#include + +namespace anime { + +class Episode { + public: + Episode(); + virtual ~Episode() {} + + void Clear(); + void Set(int anime_id); + + int anime_id; + std::wstring file; + std::wstring folder; + std::wstring format; + std::wstring title; + std::wstring clean_title; + std::wstring name; + std::wstring group; + std::wstring number; + std::wstring version; + std::wstring resolution; + std::wstring audio_type; + std::wstring video_type; + std::wstring checksum; + std::wstring extras; + std::wstring year; + bool processed; +}; + +} // namespace anime + +extern anime::Episode CurrentEpisode; + +#endif // TAIGA_LIBRARY_ANIME_EPISODE_H \ No newline at end of file diff --git a/anime_filter.cpp b/src/library/anime_filter.cpp similarity index 73% rename from anime_filter.cpp rename to src/library/anime_filter.cpp index b97f4cf17..a2a4deb12 100644 --- a/anime_filter.cpp +++ b/src/library/anime_filter.cpp @@ -1,86 +1,83 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "anime_filter.h" -#include "anime_item.h" - -#include "string.h" - -namespace anime { - -// ============================================================================= - -Filters::Filters() { - Reset(); -} - -bool Filters::CheckItem(Item& item) { - // Filter my status - for (size_t i = 0; i < my_status.size(); i++) - if (!my_status.at(i) && item.GetMyStatus() == i) - return false; - - // Filter airing status - for (size_t i = 0; i < status.size(); i++) - if (!status.at(i) && item.GetAiringStatus() == i + 1) - return false; - - // Filter type - for (size_t i = 0; i < type.size(); i++) - if (!type.at(i) && item.GetType() == i + 1) - return false; - - // Filter text - vector words; - Split(text, L" ", words); - RemoveEmptyStrings(words); - for (auto it = words.begin(); it != words.end(); ++it) { - if (InStr(item.GetTitle(), *it, 0, true) == -1 && - InStr(item.GetGenres(), *it, 0, true) == -1 && - InStr(item.GetMyTags(), *it, 0, true) == -1) { - bool found = false; - for (auto synonym = item.GetSynonyms().begin(); - !found && synonym != item.GetSynonyms().end(); ++synonym) - if (InStr(*synonym, *it, 0, true) > -1) found = true; - if (item.IsInList()) - for (auto synonym = item.GetUserSynonyms().begin(); - !found && synonym != item.GetUserSynonyms().end(); ++synonym) - if (InStr(*synonym, *it, 0, true) > -1) found = true; - if (!found) return false; - } - } - - // Item passed all filters - return true; -} - -void Filters::Reset() { - my_status.clear(); - status.clear(); - type.clear(); - - my_status.resize(7, true); - status.resize(3, true); - type.resize(6, true); - - text = L""; -} - -} // namespace anime \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/string.h" +#include "library/anime_filter.h" +#include "library/anime_item.h" + +namespace anime { + +Filters::Filters() { + Reset(); +} + +bool Filters::CheckItem(Item& item) { + // Filter my status + for (size_t i = 0; i < my_status.size(); i++) + if (!my_status.at(i) && item.GetMyStatus() == i) + return false; + + // Filter airing status + for (size_t i = 0; i < status.size(); i++) + if (!status.at(i) && item.GetAiringStatus() == i + 1) + return false; + + // Filter type + for (size_t i = 0; i < type.size(); i++) + if (!type.at(i) && item.GetType() == i + 1) + return false; + + // Filter text + std::vector words; + Split(text, L" ", words); + RemoveEmptyStrings(words); + std::wstring genres = Join(item.GetGenres(), L", "); + auto synonyms = item.GetSynonyms(); + for (auto it = words.begin(); it != words.end(); ++it) { + if (InStr(item.GetTitle(), *it, 0, true) == -1 && + InStr(genres, *it, 0, true) == -1 && + InStr(item.GetMyTags(), *it, 0, true) == -1) { + bool found = false; + for (auto synonym = synonyms.begin(); + !found && synonym != synonyms.end(); ++synonym) + if (InStr(*synonym, *it, 0, true) > -1) found = true; + if (item.IsInList()) + for (auto synonym = item.GetUserSynonyms().begin(); + !found && synonym != item.GetUserSynonyms().end(); ++synonym) + if (InStr(*synonym, *it, 0, true) > -1) found = true; + if (!found) return false; + } + } + + // Item passed all filters + return true; +} + +void Filters::Reset() { + my_status.clear(); + status.clear(); + type.clear(); + + my_status.resize(7, true); + status.resize(3, true); + type.resize(6, true); + + text = L""; +} + +} // namespace anime \ No newline at end of file diff --git a/anime_filter.h b/src/library/anime_filter.h similarity index 71% rename from anime_filter.h rename to src/library/anime_filter.h index 1bce93911..825857c6a 100644 --- a/anime_filter.h +++ b/src/library/anime_filter.h @@ -1,44 +1,45 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef ANIME_FILTER_H -#define ANIME_FILTER_H - -#include "std.h" - -namespace anime { - -class Item; - -class Filters { - public: - Filters(); - virtual ~Filters() {} - - bool CheckItem(Item& item); - void Reset(); - - vector my_status; - vector status; - vector type; - wstring text; -}; - -} // namespace anime - -#endif // ANIME_FILTER_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_LIBRARY_ANIME_FILTER_H +#define TAIGA_LIBRARY_ANIME_FILTER_H + +#include +#include + +namespace anime { + +class Item; + +class Filters { + public: + Filters(); + virtual ~Filters() {} + + bool CheckItem(Item& item); + void Reset(); + + std::vector my_status; + std::vector status; + std::vector type; + std::wstring text; +}; + +} // namespace anime + +#endif // TAIGA_LIBRARY_ANIME_FILTER_H \ No newline at end of file diff --git a/src/library/anime_item.cpp b/src/library/anime_item.cpp new file mode 100644 index 000000000..b30245666 --- /dev/null +++ b/src/library/anime_item.cpp @@ -0,0 +1,635 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include + +#include "base/foreach.h" +#include "base/string.h" +#include "base/time.h" +#include "library/anime_db.h" +#include "library/anime_item.h" +#include "library/anime_util.h" +#include "library/history.h" +#include "sync/sync.h" +#include "ui/ui.h" + +anime::Database* anime::Item::database_ = &AnimeDatabase; + +namespace anime { + +Item::Item() { + metadata_.uid.resize(sync::kLastService + 1); +} + +Item::~Item() { +} + +//////////////////////////////////////////////////////////////////////////////// + +int Item::GetId() const { + assert(!metadata_.uid.empty()); + + return ToInt(metadata_.uid.at(0)); +} + +const std::wstring& Item::GetId(enum_t service) const { + assert(metadata_.uid.size() > service); + + return metadata_.uid.at(service); +} + +const std::wstring& Item::GetSlug() const { + if (metadata_.resource.size() > 1) + return metadata_.resource.at(1); + + return EmptyString(); +} + +enum_t Item::GetSource() const { + return metadata_.source; +} + +int Item::GetType() const { + return metadata_.type; +} + +int Item::GetEpisodeCount() const { + if (metadata_.extent.size() > 0) + return metadata_.extent.at(0); + + return kUnknownEpisodeCount; +} + +int Item::GetEpisodeLength() const { + if (metadata_.extent.size() > 1) + return metadata_.extent.at(1); + + return kUnknownEpisodeLength; +} + +int Item::GetAiringStatus(bool check_date) const { + if (!check_date) + return metadata_.status; + + // TODO: Move + if (IsFinishedAiring(*this)) + return kFinishedAiring; + if (IsAiredYet(*this)) + return kAiring; + + return kNotYetAired; +} + +const std::wstring& Item::GetTitle() const { + return metadata_.title; +} + +const std::wstring& Item::GetEnglishTitle(bool fallback) const { + foreach_(it, metadata_.alternative) + if (it->type == library::kTitleTypeLangEnglish) + return it->value; + + if (fallback) + return metadata_.title; + + return EmptyString(); +} + +std::vector Item::GetSynonyms() const { + std::vector synonyms; + + foreach_(it, metadata_.alternative) + if (it->type == library::kTitleTypeSynonym) + synonyms.push_back(it->value); + + return synonyms; +} + +const Date& Item::GetDateStart() const { + if (metadata_.date.size() > 0) + return metadata_.date.at(0); + + return EmptyDate(); +} + +const Date& Item::GetDateEnd() const { + if (metadata_.date.size() > 1) + return metadata_.date.at(1); + + return EmptyDate(); +} + +const std::wstring& Item::GetImageUrl() const { + if (metadata_.resource.size() > 0) + return metadata_.resource.at(0); + + return EmptyString(); +} + +enum_t Item::GetAgeRating() const { + return metadata_.audience; +} + +const std::vector& Item::GetGenres() const { + return metadata_.subject; +} + +const std::wstring& Item::GetPopularity() const { + if (metadata_.community.size() > 1) + return metadata_.community.at(1); + + return EmptyString(); +} + +const std::vector& Item::GetProducers() const { + return metadata_.creator; +} + +const std::wstring& Item::GetScore() const { + if (metadata_.community.size() > 0) + return metadata_.community.at(0); + + return EmptyString(); +} + +const std::wstring& Item::GetSynopsis() const { + return metadata_.description; +} + +const time_t Item::GetLastModified() const { + return metadata_.modified; +} + +//////////////////////////////////////////////////////////////////////////////// + +void Item::SetId(const std::wstring& id, enum_t service) { + if (metadata_.uid.size() < static_cast(service) + 1) + metadata_.uid.resize(service + 1); + + metadata_.uid.at(service) = id; +} + +void Item::SetSlug(const std::wstring& slug) { + if (metadata_.resource.size() < 2) + metadata_.resource.resize(2); + + metadata_.resource.at(1) = slug; +} + +void Item::SetSource(enum_t source) { + metadata_.source = source; +} + +void Item::SetType(int type) { + metadata_.type = type; +} + +void Item::SetEpisodeCount(int number) { + if (metadata_.extent.size() < 1) + metadata_.extent.resize(1); + + metadata_.extent.at(0) = number; + + // TODO: Call it separately + if (number >= 0) + if (static_cast(number) > local_info_.available_episodes.size()) + local_info_.available_episodes.resize(number); +} + +void Item::SetEpisodeLength(int number) { + if (metadata_.extent.size() < 2) + metadata_.extent.resize(2); + + metadata_.extent.at(1) = number; +} + +void Item::SetAiringStatus(int status) { + metadata_.status = status; +} + +void Item::SetTitle(const std::wstring& title) { + metadata_.title = title; +} + +void Item::SetEnglishTitle(const std::wstring& title) { + foreach_(it, metadata_.alternative) { + if (it->type == library::kTitleTypeLangEnglish) { + it->value = title; + return; + } + } + + library::Title new_title; + new_title.type = library::kTitleTypeLangEnglish; + new_title.value = title; + + metadata_.alternative.push_back(new_title); +} + +void Item::SetSynonyms(const std::wstring& synonyms) { + std::vector temp; + Split(synonyms, L"; ", temp); + RemoveEmptyStrings(temp); + + SetSynonyms(temp); +} + +void Item::SetSynonyms(const std::vector& synonyms) { + std::vector alternative; + + foreach_(it, metadata_.alternative) + if (it->type != library::kTitleTypeSynonym) + alternative.push_back(*it); + + foreach_(it, synonyms) { + library::Title new_title; + new_title.type = library::kTitleTypeSynonym; + new_title.value = *it; + alternative.push_back(new_title); + } + + metadata_.alternative = alternative; +} + +void Item::SetDateStart(const Date& date) { + if (metadata_.date.size() < 1) + metadata_.date.resize(1); + + metadata_.date.at(0) = date; +} + +void Item::SetDateEnd(const Date& date) { + if (metadata_.date.size() < 2) + metadata_.date.resize(2); + + metadata_.date.at(1) = date; +} + +void Item::SetImageUrl(const std::wstring& url) { + if (metadata_.resource.size() < 1) + metadata_.resource.resize(1); + + metadata_.resource.at(0) = url; +} + +void Item::SetAgeRating(enum_t rating) { + metadata_.audience = rating; +} + +void Item::SetGenres(const std::wstring& genres) { + std::vector temp; + Split(genres, L", ", temp); + RemoveEmptyStrings(temp); + + SetGenres(temp); +} + +void Item::SetGenres(const std::vector& genres) { + metadata_.subject = genres; +} + +void Item::SetPopularity(const std::wstring& popularity) { + if (metadata_.community.size() < 2) + metadata_.community.resize(2); + + metadata_.community.at(1) = popularity; +} + +void Item::SetProducers(const std::wstring& producers) { + std::vector temp; + Split(producers, L", ", temp); + RemoveEmptyStrings(temp); + + SetProducers(temp); +} + +void Item::SetProducers(const std::vector& producers) { + metadata_.creator = producers; +} + +void Item::SetScore(const std::wstring& score) { + if (metadata_.community.size() < 1) + metadata_.community.resize(1); + + metadata_.community.at(0) = score; +} + +void Item::SetSynopsis(const std::wstring& synopsis) { + metadata_.description = synopsis; +} + +void Item::SetLastModified(time_t modified) { + metadata_.modified = modified; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int Item::GetMyLastWatchedEpisode(bool check_queue) const { + if (!my_info_.get()) + return 0; + + HistoryItem* history_item = check_queue ? + SearchHistory(kQueueSearchEpisode) : nullptr; + + return history_item ? *history_item->episode : my_info_->watched_episodes; +} + +int Item::GetMyScore(bool check_queue) const { + if (!my_info_.get()) + return 0; + + HistoryItem* history_item = check_queue ? + SearchHistory(kQueueSearchScore) : nullptr; + + return history_item ? *history_item->score : my_info_->score; +} + +int Item::GetMyStatus(bool check_queue) const { + if (!my_info_.get()) + return kNotInList; + + HistoryItem* history_item = check_queue ? + SearchHistory(kQueueSearchStatus) : nullptr; + + return history_item ? *history_item->status : my_info_->status; +} + +int Item::GetMyRewatching(bool check_queue) const { + if (!my_info_.get()) + return FALSE; + + HistoryItem* history_item = check_queue ? + SearchHistory(kQueueSearchRewatching) : nullptr; + + return history_item ? *history_item->enable_rewatching : my_info_->rewatching; +} + +int Item::GetMyRewatchingEp() const { + if (!my_info_.get()) + return 0; + + return my_info_->rewatching_ep; +} + +const Date& Item::GetMyDateStart(bool check_queue) const { + if (!my_info_.get()) + return EmptyDate(); + + HistoryItem* history_item = check_queue ? + SearchHistory(kQueueSearchDateStart) : nullptr; + + return history_item ? *history_item->date_start : my_info_->date_start; +} + +const Date& Item::GetMyDateEnd(bool check_queue) const { + if (!my_info_.get()) + return EmptyDate(); + + HistoryItem* history_item = check_queue ? + SearchHistory(kQueueSearchDateEnd) : nullptr; + + return history_item ? *history_item->date_finish : my_info_->date_finish; +} + +const std::wstring& Item::GetMyLastUpdated() const { + if (!my_info_.get()) + return EmptyString(); + + return my_info_->last_updated; +} + +const std::wstring& Item::GetMyTags(bool check_queue) const { + if (!my_info_.get()) + return EmptyString(); + + HistoryItem* history_item = check_queue ? + SearchHistory(kQueueSearchTags) : nullptr; + + return history_item ? *history_item->tags : my_info_->tags; +} + +//////////////////////////////////////////////////////////////////////////////// + +void Item::SetMyLastWatchedEpisode(int number) { + assert(my_info_.get()); + + my_info_->watched_episodes = number; +} + +void Item::SetMyScore(int score) { + assert(my_info_.get()); + + my_info_->score = score; +} + +void Item::SetMyStatus(int status) { + assert(my_info_.get()); + + my_info_->status = status; +} + +void Item::SetMyRewatching(int rewatching) { + assert(my_info_.get()); + + my_info_->rewatching = rewatching; +} + +void Item::SetMyRewatchingEp(int rewatching_ep) { + assert(my_info_.get()); + + my_info_->rewatching_ep = rewatching_ep; +} + +void Item::SetMyDateStart(const Date& date) { + assert(my_info_.get()); + + my_info_->date_start = date; +} + +void Item::SetMyDateEnd(const Date& date) { + assert(my_info_.get()); + + my_info_->date_finish = date; +} + +void Item::SetMyLastUpdated(const std::wstring& last_updated) { + assert(my_info_.get()); + + my_info_->last_updated = last_updated; +} + +void Item::SetMyTags(const std::wstring& tags) { + assert(my_info_.get()); + + my_info_->tags = tags; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +int Item::GetAvailableEpisodeCount() const { + return static_cast(local_info_.available_episodes.size()); +} + +const std::wstring& Item::GetFolder() const { + return local_info_.folder; +} + +int Item::GetLastAiredEpisodeNumber(bool estimate) { + if (local_info_.last_aired_episode) + return local_info_.last_aired_episode; + + // No need to estimate if the series isn't currently airing + switch (GetAiringStatus()) { + case kFinishedAiring: + local_info_.last_aired_episode = GetEpisodeCount(); + return local_info_.last_aired_episode; + case kNotYetAired: + case kUnknownStatus: + return 0; + } + + if (estimate) + return EstimateLastAiredEpisodeNumber(*this); + + return local_info_.last_aired_episode; +} + +const std::wstring& Item::GetNextEpisodePath() const { + return local_info_.next_episode_path; +} + +bool Item::GetPlaying() const { + return local_info_.playing; +} + +bool Item::GetUseAlternative() const { + return local_info_.use_alternative; +} + +const std::vector& Item::GetUserSynonyms() const { + return local_info_.synonyms; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool Item::SetEpisodeAvailability(int number, bool available, + const std::wstring& path) { + if (number == 0) + number = 1; + + if (number <= GetEpisodeCount() || GetEpisodeCount() == 0) { + if (static_cast(number) > local_info_.available_episodes.size()) { + local_info_.available_episodes.resize(number); + } + local_info_.available_episodes.at(number - 1) = available; + if (number == GetMyLastWatchedEpisode() + 1) { + SetNextEpisodePath(path); + } + + ui::OnLibraryEntryChange(GetId()); + + return true; + } + + return false; +} + +void Item::SetFolder(const std::wstring& folder) { + local_info_.folder = folder; +} + +void Item::SetLastAiredEpisodeNumber(int number) { + if (number > local_info_.last_aired_episode) + local_info_.last_aired_episode = number; +} + +void Item::SetNextEpisodePath(const std::wstring& path) { + local_info_.next_episode_path = path; +} + +void Item::SetPlaying(bool playing) { + local_info_.playing = playing; +} + +void Item::SetUseAlternative(bool use_alternative) { + local_info_.use_alternative = use_alternative; +} + +void Item::SetUserSynonyms(const std::wstring& synonyms) { + std::vector temp; + Split(synonyms, L"; ", temp); + + SetUserSynonyms(temp); +} + +void Item::SetUserSynonyms(const std::vector& synonyms) { + local_info_.synonyms = synonyms; + RemoveEmptyStrings(local_info_.synonyms); + + if (!synonyms.empty() && CurrentEpisode.anime_id == anime::ID_NOTINLIST) { + CurrentEpisode.Set(anime::ID_UNKNOWN); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +bool Item::IsEpisodeAvailable(int number) const { + if (number < 1) + number = 1; + if (static_cast(number) > local_info_.available_episodes.size()) + return false; + + return local_info_.available_episodes.at(number - 1); +} + +bool Item::IsNewEpisodeAvailable() const { + return IsEpisodeAvailable(GetMyLastWatchedEpisode() + 1); +} + +bool Item::UserSynonymsAvailable() const { + return !local_info_.synonyms.empty(); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +void Item::AddtoUserList() { + if (!my_info_.get()) { + my_info_.reset(new MyInformation); + } +} + +bool Item::IsInList() const { + return my_info_.get() && GetMyStatus() != kNotInList; +} + +void Item::RemoveFromUserList() { + assert(my_info_.use_count() <= 1); + my_info_.reset(); + assert(my_info_.use_count() == 0); +} + +//////////////////////////////////////////////////////////////////////////////// + +HistoryItem* Item::SearchHistory(int search_mode) const { + return History.queue.FindItem(GetId(), search_mode); +} + +} // namespace anime \ No newline at end of file diff --git a/src/library/anime_item.h b/src/library/anime_item.h new file mode 100644 index 000000000..8c57ac504 --- /dev/null +++ b/src/library/anime_item.h @@ -0,0 +1,167 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_LIBRARY_ANIME_ITEM_H +#define TAIGA_LIBRARY_ANIME_ITEM_H + +#include +#include +#include + +#include "anime.h" +#include "metadata.h" + +namespace anime { +class Database; +class Episode; +class Item; +} +class Date; +class HistoryItem; + +namespace anime { + +class Item { +public: + Item(); + virtual ~Item(); + + ////////////////////////////////////////////////////////////////////////////// + // Metadata + + int GetId() const; + const std::wstring& GetId(enum_t service) const; + const std::wstring& GetSlug() const; + enum_t GetSource() const; + int GetType() const; + int GetEpisodeCount() const; + int GetEpisodeLength() const; + int GetAiringStatus(bool check_date = true) const; + const std::wstring& GetTitle() const; + const std::wstring& GetEnglishTitle(bool fallback = false) const; + std::vector GetSynonyms() const; + const Date& GetDateStart() const; + const Date& GetDateEnd() const; + const std::wstring& GetImageUrl() const; + enum_t GetAgeRating() const; + const std::vector& GetGenres() const; + const std::wstring& GetPopularity() const; + const std::vector& GetProducers() const; + const std::wstring& GetScore() const; + const std::wstring& GetSynopsis() const; + const time_t GetLastModified() const; + + void SetId(const std::wstring& id, enum_t service); + void SetSlug(const std::wstring& slug); + void SetSource(enum_t source); + void SetType(int type); + void SetEpisodeCount(int number); + void SetEpisodeLength(int number); + void SetAiringStatus(int status); + void SetTitle(const std::wstring& title); + void SetEnglishTitle(const std::wstring& title); + void SetSynonyms(const std::wstring& synonyms); + void SetSynonyms(const std::vector& synonyms); + void SetDateStart(const Date& date); + void SetDateEnd(const Date& date); + void SetImageUrl(const std::wstring& url); + void SetAgeRating(enum_t rating); + void SetGenres(const std::wstring& genres); + void SetGenres(const std::vector& genres); + void SetPopularity(const std::wstring& popularity); + void SetProducers(const std::wstring& producers); + void SetProducers(const std::vector& producers); + void SetScore(const std::wstring& score); + void SetSynopsis(const std::wstring& synopsis); + void SetLastModified(time_t modified); + + ////////////////////////////////////////////////////////////////////////////// + // Library data + + int GetMyLastWatchedEpisode(bool check_queue = true) const; + int GetMyScore(bool check_queue = true) const; + int GetMyStatus(bool check_queue = true) const; + int GetMyRewatching(bool check_queue = true) const; + int GetMyRewatchingEp() const; + const Date& GetMyDateStart(bool check_queue = true) const; + const Date& GetMyDateEnd(bool check_queue = true) const; + const std::wstring& GetMyLastUpdated() const; + const std::wstring& GetMyTags(bool check_queue = true) const; + + void SetMyLastWatchedEpisode(int number); + void SetMyScore(int score); + void SetMyStatus(int status); + void SetMyRewatching(int rewatching); + void SetMyRewatchingEp(int rewatching_ep); + void SetMyDateStart(const Date& date); + void SetMyDateEnd(const Date& date); + void SetMyLastUpdated(const std::wstring& last_updated); + void SetMyTags(const std::wstring& tags); + + ////////////////////////////////////////////////////////////////////////////// + // Local data + + int GetAvailableEpisodeCount() const; + const std::wstring& GetFolder() const; + int GetLastAiredEpisodeNumber(bool estimate = false); + const std::wstring& GetNextEpisodePath() const; + bool GetPlaying() const; + bool GetUseAlternative() const; + const std::vector& GetUserSynonyms() const; + + bool SetEpisodeAvailability(int number, bool available, const std::wstring& path); + void SetFolder(const std::wstring& folder); + void SetLastAiredEpisodeNumber(int number); + void SetNextEpisodePath(const std::wstring& path); + void SetPlaying(bool playing); + void SetUseAlternative(bool use_alternative); + void SetUserSynonyms(const std::wstring& synonyms); + void SetUserSynonyms(const std::vector& synonyms); + + bool IsEpisodeAvailable(int number) const; + bool IsNewEpisodeAvailable() const; + bool UserSynonymsAvailable() const; + + ////////////////////////////////////////////////////////////////////////////// + + // A database item may not be in user's list. + void AddtoUserList(); + bool IsInList() const; + void RemoveFromUserList(); + +private: + // Helper function + HistoryItem* SearchHistory(int search_mode) const; + + // Series information, stored in db\anime.xml + library::Metadata metadata_; + + // User information, stored in user\\anime.xml - some items are not + // in user's list, thus this member is not valid for every item. + std::shared_ptr my_info_; + + // Local information, stored temporarily + LocalInformation local_info_; + + // Pointer to the parent database which holds this item + static Database* database_; +}; + +} // namespace anime + +#endif // TAIGA_LIBRARY_ANIME_ITEM_H \ No newline at end of file diff --git a/src/library/anime_util.cpp b/src/library/anime_util.cpp new file mode 100644 index 000000000..647281422 --- /dev/null +++ b/src/library/anime_util.cpp @@ -0,0 +1,769 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/file.h" +#include "base/foreach.h" +#include "base/log.h" +#include "base/process.h" +#include "base/string.h" +#include "library/anime.h" +#include "library/anime_db.h" +#include "library/anime_episode.h" +#include "library/anime_util.h" +#include "library/history.h" +#include "sync/sync.h" +#include "taiga/announce.h" +#include "taiga/path.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" +#include "taiga/timer.h" +#include "track/feed.h" +#include "track/media.h" +#include "track/recognition.h" +#include "track/search.h" +#include "ui/ui.h" + +namespace anime { + +bool IsAiredYet(const Item& item) { + if (item.GetAiringStatus(false) != kNotYetAired) + return true; + + if (!IsValidDate(item.GetDateStart())) + return false; + + Date date_japan = GetDateJapan(); + Date date_start = item.GetDateStart(); + + // Assume the worst case + if (!date_start.month) + date_start.month = 12; + if (!date_start.day) + date_start.day = 31; + + return date_japan >= date_start; +} + +bool IsFinishedAiring(const Item& item) { + if (item.GetAiringStatus(false) == kFinishedAiring) + return true; + + if (!IsValidDate(item.GetDateEnd())) + return false; + + if (!IsAiredYet(item)) + return false; + + return GetDateJapan() > item.GetDateEnd(); +} + +int EstimateLastAiredEpisodeNumber(const Item& item) { + // Can't estimate for other types of anime + if (item.GetType() != kTv) + return 0; + + // TV series air weekly, so the number of weeks that has passed since the day + // the series started airing gives us the last aired episode. Note that + // irregularities such as broadcasts being postponed due to sports events make + // this method unreliable. + const Date& date_start = item.GetDateStart(); + if (date_start.year && date_start.month && date_start.day) { + // To compensate for the fact that we don't know the airing hour, + // we substract one more day. + int date_diff = GetDateJapan() - date_start - 1; + if (date_diff > -1) { + int number_of_weeks = date_diff / 7; + if (number_of_weeks < item.GetEpisodeCount()) { + return number_of_weeks + 1; + } else { + return item.GetEpisodeCount(); + } + } + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// + +// An item's series information will only be updated only if its last modified +// value is significantly older than the new one's. This helps us lower +// the number of requests we send to a service. + +bool IsItemOldEnough(const Item& item) { + if (!item.GetLastModified()) + return true; + + time_t time_diff = time(nullptr) - item.GetLastModified(); + + if (item.GetAiringStatus() == kFinishedAiring) { + return time_diff >= 60 * 60 * 24 * 7; // 1 week + } else { + return time_diff >= 60 * 60; // 1 hour + } +} + +bool MetadataNeedsRefresh(const Item& item) { + if (IsItemOldEnough(item)) + return true; + + if (item.GetSynopsis().empty()) + return true; + if (item.GetGenres().empty()) + return true; + if (item.GetScore().empty() && + taiga::GetCurrentServiceId() == sync::kMyAnimeList) + return true; + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool IsNsfw(const Item& item) { + if (item.GetAgeRating() == anime::kAgeRatingR18) + return true; + + if (item.GetAgeRating() == anime::kUnknownAgeRating) { + auto& genres = item.GetGenres(); + if (std::find(genres.begin(), genres.end(), L"Hentai") != genres.end()) + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool PlayEpisode(int anime_id, int number) { + auto anime_item = AnimeDatabase.FindItem(anime_id); + + if (!anime_item) + return false; + + if (number > anime_item->GetEpisodeCount() && + anime_item->GetEpisodeCount() != 0) + return false; + + if (number == 0) + number = 1; + + std::wstring file_path; + + // Check saved episode path + if (number == anime_item->GetMyLastWatchedEpisode() + 1) { + const std::wstring& next_episode_path = anime_item->GetNextEpisodePath(); + if (!next_episode_path.empty()) { + if (FileExists(next_episode_path)) { + file_path = next_episode_path; + } else { + LOG(LevelWarning, L"File doesn't exist anymore."); + LOG(LevelWarning, L"Path: " + next_episode_path); + anime_item->SetEpisodeAvailability(number, false, L""); + } + } + } + + // Scan available episodes + if (file_path.empty()) { + ScanAvailableEpisodes(false, anime_item->GetId(), number); + if (anime_item->IsEpisodeAvailable(number)) { + file_path = file_search_helper.path_found(); + } + } + + if (file_path.empty()) { + ui::ChangeStatusText(L"Could not find episode #" + ToWstr(number) + + L" (" + anime_item->GetTitle() + L")."); + } else { + Execute(file_path); + } + + return !file_path.empty(); +} + +bool PlayLastEpisode(int anime_id) { + auto anime_item = AnimeDatabase.FindItem(anime_id); + + if (!anime_item) + return false; + + return PlayEpisode(anime_id, anime_item->GetMyLastWatchedEpisode()); +} + +bool PlayNextEpisode(int anime_id) { + auto anime_item = AnimeDatabase.FindItem(anime_id); + + if (!anime_item) + return false; + + if (anime_item->GetEpisodeCount() != 1) { + return PlayEpisode(anime_id, anime_item->GetMyLastWatchedEpisode() + 1); + } else { + return PlayEpisode(anime_id, 1); + } +} + +bool PlayRandomAnime() { + static time_t time_last_checked = 0; + time_t time_now = time(nullptr); + if (time_now > time_last_checked + (60 * 2)) { // 2 minutes + ScanAvailableEpisodesQuick(); + time_last_checked = time_now; + } + + std::vector valid_ids; + + foreach_(it, AnimeDatabase.items) { + anime::Item& anime_item = it->second; + if (!anime_item.IsInList()) + continue; + if (!anime_item.IsNewEpisodeAvailable()) + continue; + switch (anime_item.GetMyStatus()) { + case anime::kNotInList: + case anime::kCompleted: + case anime::kDropped: + continue; + } + valid_ids.push_back(anime_item.GetId()); + } + + foreach_ (id, valid_ids) { + srand(static_cast(GetTickCount())); + size_t max_value = valid_ids.size(); + size_t index = rand() % max_value + 1; + int anime_id = valid_ids.at(index); + if (PlayNextEpisode(anime_id)) + return true; + } + + ui::OnAnimeEpisodeNotFound(); + return false; +} + +bool PlayRandomEpisode(Item& item) { + const int total = item.GetMyStatus() == kCompleted ? + item.GetEpisodeCount() : item.GetMyLastWatchedEpisode() + 1; + const int max_tries = item.GetFolder().empty() ? 3 : 10; + + srand(static_cast(GetTickCount())); + + for (int i = 0; i < min(total, max_tries); i++) { + int episode_number = rand() % total + 1; + if (PlayEpisode(item.GetId(), episode_number)) + return true; + } + + ui::OnAnimeEpisodeNotFound(); + return false; +} + +bool LinkEpisodeToAnime(Episode& episode, int anime_id) { + auto anime_item = AnimeDatabase.FindItem(anime_id); + + if (!anime_item) + return false; + + episode.anime_id = anime_id; + anime_item->AddtoUserList(); + + auto synonyms = anime_item->GetUserSynonyms(); + synonyms.push_back(CurrentEpisode.title); + anime_item->SetUserSynonyms(synonyms); + Meow.UpdateCleanTitles(anime_item->GetId()); + Settings.Save(); + + StartWatching(*anime_item, episode); + ui::ClearStatusText(); + + return true; +} + +void StartWatching(Item& item, Episode& episode) { + // Make sure item is in list + if (!item.IsInList()) + item.AddtoUserList(); + + // Change status + Taiga.play_status = taiga::kPlayStatusPlaying; + item.SetPlaying(true); + + ui::OnAnimeWatchingStart(item, episode); + + // Check folder + if (item.GetFolder().empty()) { + if (episode.folder.empty()) { + HWND hwnd = MediaPlayers.GetCurrentWindowHandle(); + episode.folder = GetPathOnly(MediaPlayers.GetTitleFromProcessHandle(hwnd)); + } + if (IsInsideRootFolders(episode.folder)) { + // Set the folder if only it is under a root folder + item.SetFolder(episode.folder); + Settings.Save(); + } + } + + // Get additional information + if (item.GetScore().empty() || item.GetSynopsis().empty()) + sync::GetMetadataById(item.GetId()); + + // Update list + if (Settings.GetInt(taiga::kSync_Update_Delay) == 0 && + !Settings.GetBool(taiga::kSync_Update_WaitPlayer)) + UpdateList(item, episode); +} + +void EndWatching(Item& item, Episode episode) { + // Change status + Taiga.play_status = taiga::kPlayStatusStopped; + item.SetPlaying(false); + + // Announce + episode.anime_id = item.GetId(); + Announcer.Do(taiga::kAnnounceToHttp, &episode); + Announcer.Clear(taiga::kAnnounceToSkype); + + episode.anime_id = anime::ID_UNKNOWN; + + ui::OnAnimeWatchingEnd(item, episode); +} + +//////////////////////////////////////////////////////////////////////////////// + +bool IsUpdateAllowed(Item& item, const Episode& episode, bool ignore_update_time) { + if (episode.processed) + return false; + + if (!ignore_update_time) { + auto delay = Settings.GetInt(taiga::kSync_Update_Delay); + auto ticks = taiga::timers.timer(taiga::kTimerMedia)->ticks(); + if (delay > 0 && ticks > 0) + return false; + } + + if (item.GetMyStatus() == kCompleted && item.GetMyRewatching() == 0) + return false; + + int number = GetEpisodeHigh(episode.number); + int number_low = GetEpisodeLow(episode.number); + int last_watched = item.GetMyLastWatchedEpisode(); + + if (Settings.GetBool(taiga::kSync_Update_OutOfRange)) + if (number_low > last_watched + 1 || number < last_watched + 1) + return false; + + if (!IsValidEpisode(number, last_watched, item.GetEpisodeCount())) + return false; + + return true; +} + +void UpdateList(Item& item, Episode& episode) { + if (!IsUpdateAllowed(item, episode, false)) + return; + + episode.processed = true; + + if (Settings.GetBool(taiga::kSync_Update_AskToConfirm)) { + ConfirmationQueue.Add(episode); + ConfirmationQueue.Process(); + } else { + AddToQueue(item, episode, true); + } +} + +void AddToQueue(Item& item, const Episode& episode, bool change_status) { + // Create history item + HistoryItem history_item; + history_item.anime_id = item.GetId(); + + // Set episode number + history_item.episode = GetEpisodeHigh(episode.number); + + // Set start/finish date + if (*history_item.episode == 1 && !IsValidDate(item.GetMyDateStart())) + history_item.date_start = GetDate(); + if (*history_item.episode == item.GetEpisodeCount() && !IsValidDate(item.GetMyDateEnd())) + history_item.date_finish = GetDate(); + + // Set update mode + if (item.GetMyStatus() == kNotInList) { + history_item.mode = taiga::kHttpServiceAddLibraryEntry; + change_status = true; + } else { + history_item.mode = taiga::kHttpServiceUpdateLibraryEntry; + } + + if (change_status) { + // Move to completed + if (item.GetEpisodeCount() == *history_item.episode) { + history_item.status = kCompleted; + if (item.GetMyRewatching()) { + history_item.enable_rewatching = FALSE; + //history_item.times_rewatched++; // TODO: Enable when MAL adds to API + } + // Move to watching + } else if (item.GetMyStatus() != kWatching || *history_item.episode == 1) { + if (!item.GetMyRewatching()) { + history_item.status = kWatching; + } + } + } + + // Add to queue + History.queue.Add(history_item); +} + +//////////////////////////////////////////////////////////////////////////////// + +bool GetFansubFilter(int anime_id, std::vector& groups) { + bool found = false; + + foreach_(i, Aggregator.filter_manager.filters) { + if (found) break; + foreach_(j, i->anime_ids) { + if (*j != anime_id) continue; + if (found) break; + foreach_(k, i->conditions) { + if (k->element == kFeedFilterElement_Episode_Group) { + groups.push_back(k->value); + found = true; + } + } + } + } + + return found; +} + +bool SetFansubFilter(int anime_id, const std::wstring& group_name) { + // Check existing filters + foreach_(i, Aggregator.filter_manager.filters) { + foreach_(j, i->anime_ids) { + if (*j != anime_id) continue; + foreach_(k, i->conditions) { + if (k->element == kFeedFilterElement_Episode_Group) { + if (group_name.empty()) { + Aggregator.filter_manager.filters.erase(i); + } else { + k->value = group_name; + } + return true; + } + } + } + } + + if (group_name.empty()) + return false; + + // Create new filter + auto anime_item = AnimeDatabase.FindItem(anime_id); + Aggregator.filter_manager.AddFilter( + kFeedFilterActionPrefer, kFeedFilterMatchAll, kFeedFilterOptionDefault, + true, L"[Fansub] " + anime_item->GetTitle()); + Aggregator.filter_manager.filters.back().AddCondition( + kFeedFilterElement_Episode_Group, kFeedFilterOperator_Equals, + group_name); + Aggregator.filter_manager.filters.back().anime_ids.push_back(anime_id); + return true; +} + +std::wstring GetImagePath(int anime_id) { + std::wstring path = taiga::GetPath(taiga::kPathDatabaseImage); + if (anime_id > 0) path += ToWstr(anime_id) + L".jpg"; + return path; +} + +void GetUpcomingTitles(std::vector& anime_ids) { + foreach_c_(item, AnimeDatabase.items) { + const anime::Item& anime_item = item->second; + + const Date& date_start = anime_item.GetDateStart(); + const Date& date_now = GetDateJapan(); + + if (!date_start.year || !date_start.month || !date_start.day) + continue; + + if (date_start > date_now && + ToDayCount(date_start) < ToDayCount(date_now) + 7) { // Same week + anime_ids.push_back(anime_item.GetId()); + } + } +} + +bool IsInsideRootFolders(const std::wstring& path) { + foreach_c_(root_folder, Settings.root_folders) + if (StartsWith(path, *root_folder)) + return true; + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + +int GetEpisodeHigh(const std::wstring& episode_number) { + int value = 1; + int pos = InStrRev(episode_number, L"-", episode_number.length()); + + if (pos == episode_number.length() - 1) { + value = ToInt(episode_number.substr(0, pos)); + } else if (pos > -1) { + value = ToInt(episode_number.substr(pos + 1)); + } else { + value = ToInt(episode_number); + } + + return value; +} + +int GetEpisodeLow(const std::wstring& episode_number) { + return ToInt(episode_number); // ToInt() stops at "-" +} + +bool IsAllEpisodesAvailable(const Item& item) { + if (item.GetEpisodeCount() == 0) + return false; + + int available_episode_count = item.GetAvailableEpisodeCount(); + bool all_episodes_available = available_episode_count > 0; + + for (int i = 1; i <= available_episode_count; i++) { + if (!item.IsEpisodeAvailable(i)) { + all_episodes_available = false; + break; + } + } + + return all_episodes_available; +} + +bool IsEpisodeRange(const std::wstring& episode_number) { + return GetEpisodeLow(episode_number) != GetEpisodeHigh(episode_number); +} + +bool IsValidEpisode(int episode, int total) { + if ((episode < 0) || + (episode > total && total != 0)) + return false; + + return true; +} + +bool IsValidEpisode(int episode, int watched, int total) { + if (!IsValidEpisode(episode, total) || + (episode < watched) || + (episode == watched && total != 1)) + return false; + + return true; +} + +std::wstring JoinEpisodeNumbers(const std::vector& input) { + std::wstring output; + + foreach_(it, input) { + if (!output.empty()) + output += L"-"; + output += ToWstr(*it); + } + + return output; +} + +void SplitEpisodeNumbers(const std::wstring& input, std::vector& output) { + if (input.empty()) + return; + + std::vector numbers; + Split(input, L"-", numbers); + + foreach_(it, numbers) + output.push_back(ToInt(*it)); +} + +int EstimateEpisodeCount(const Item& item) { + // If we already know the number, we don't need to estimate + if (item.GetEpisodeCount() > 0) + return item.GetEpisodeCount(); + + int number = 0; + + // Estimate using user information + if (item.IsInList()) + number = max(item.GetMyLastWatchedEpisode(), + item.GetAvailableEpisodeCount()); + + // Estimate using airing dates of TV series + if (item.GetType() == kTv) { + Date date_start = item.GetDateStart(); + if (IsValidDate(date_start)) { + Date date_end = item.GetDateEnd(); + // Use current date in Japan if ending date is unknown + if (!IsValidDate(date_end)) date_end = GetDateJapan(); + // Assuming the series is aired weekly + number = max(number, (date_end - date_start) / 7); + } + } + + // Given all TV series aired since 2000, most them have their episodes + // spanning one or two seasons. Following is a table of top ten values: + // + // Episodes Seasons Percent + // ------------------------------ + // 12 1 23.6% + // 13 1 20.2% + // 26 2 15.4% + // 24 2 6.4% + // 25 2 5.0% + // 52 4 4.4% + // 51 4 3.1% + // 11 1 2.6% + // 50 4 2.3% + // 39 3 1.4% + // ------------------------------ + // Total: 84.6% + // + // With that in mind, we can normalize our output at several points. + if (number < 12) return 13; + if (number < 24) return 26; + if (number < 50) return 52; + + // This is a series that has aired for more than a year, which means we cannot + // estimate for how long it is going to continue. + return 0; +} + +void ChangeEpisode(int anime_id, int value) { + auto anime_item = AnimeDatabase.FindItem(anime_id); + + if (!anime_item) + return; + + if (IsValidEpisode(value, -1, anime_item->GetEpisodeCount())) { + Episode episode; + episode.number = ToWstr(value); + AddToQueue(*anime_item, episode, true); + } +} + +void DecrementEpisode(int anime_id) { + auto anime_item = AnimeDatabase.FindItem(anime_id); + + if (!anime_item) + return; + + int watched = anime_item->GetMyLastWatchedEpisode(); + auto history_item = History.queue.FindItem(anime_item->GetId(), + kQueueSearchEpisode); + + if (history_item && *history_item->episode == watched && + watched > anime_item->GetMyLastWatchedEpisode(false)) { + history_item->enabled = false; + History.queue.RemoveDisabled(); + } else { + if (IsValidEpisode(watched - 1, -1, anime_item->GetEpisodeCount())) { + Episode episode; + episode.number = ToWstr(watched - 1); + AddToQueue(*anime_item, episode, true); + } + } +} + +void IncrementEpisode(int anime_id) { + auto anime_item = AnimeDatabase.FindItem(anime_id); + + if (!anime_item) + return; + + int watched = anime_item->GetMyLastWatchedEpisode(); + + if (IsValidEpisode(watched + 1, watched, anime_item->GetEpisodeCount())) { + Episode episode; + episode.number = ToWstr(watched + 1); + AddToQueue(*anime_item, episode, true); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +std::wstring TranslateMyStatus(int value, bool add_count) { + #define ADD_COUNT() (add_count ? L" (" + ToWstr(AnimeDatabase.GetItemCount(value)) + L")" : L"") + switch (value) { + case kNotInList: return L"Not in list"; + case kWatching: return L"Currently watching" + ADD_COUNT(); + case kCompleted: return L"Completed" + ADD_COUNT(); + case kOnHold: return L"On hold" + ADD_COUNT(); + case kDropped: return L"Dropped" + ADD_COUNT(); + case kPlanToWatch: return L"Plan to watch" + ADD_COUNT(); + default: return L""; + } + #undef ADD_COUNT +} + +std::wstring TranslateNumber(int value, const std::wstring& default_char) { + return value > 0 ? ToWstr(value) : default_char; +} + +std::wstring TranslateStatus(int value) { + switch (value) { + case kAiring: return L"Currently airing"; + case kFinishedAiring: return L"Finished airing"; + case kNotYetAired: return L"Not yet aired"; + default: return ToWstr(value); + } +} + +std::wstring TranslateType(int value) { + switch (value) { + case kTv: return L"TV"; + case kOva: return L"OVA"; + case kMovie: return L"Movie"; + case kSpecial: return L"Special"; + case kOna: return L"ONA"; + case kMusic: return L"Music"; + default: return L""; + } +} + +int TranslateResolution(const std::wstring& str, bool return_validity) { + // *###x###* + if (str.length() > 6) { + int pos = InStr(str, L"x", 0); + if (pos > -1) { + for (unsigned int i = 0; i < str.length(); i++) + if (i != pos && !IsNumeric(str.at(i))) + return 0; + return return_validity ? TRUE : ToInt(str.substr(pos + 1)); + } + + // *###p + } else if (str.length() > 3) { + if (str.at(str.length() - 1) == 'p') { + for (unsigned int i = 0; i < str.length() - 1; i++) + if (!IsNumeric(str.at(i))) + return 0; + return return_validity ? TRUE : ToInt(str.substr(0, str.length() - 1)); + } + } + + return 0; +} + +} // namespace anime \ No newline at end of file diff --git a/src/library/anime_util.h b/src/library/anime_util.h new file mode 100644 index 000000000..4b9897fdb --- /dev/null +++ b/src/library/anime_util.h @@ -0,0 +1,117 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_LIBRARY_ANIME_UTIL_H +#define TAIGA_LIBRARY_ANIME_UTIL_H + +#include +#include + +#include "base/comparable.h" + +class Date; + +namespace anime { + +class Episode; +class Item; + +bool IsAiredYet(const Item& item); +bool IsFinishedAiring(const Item& item); +int EstimateLastAiredEpisodeNumber(const Item& item); + +bool IsItemOldEnough(const Item& item); +bool MetadataNeedsRefresh(const Item& item); + +bool IsNsfw(const Item& item); + +bool PlayEpisode(int anime_id, int number); +bool PlayLastEpisode(int anime_id); +bool PlayNextEpisode(int anime_id); +bool PlayRandomAnime(); +bool PlayRandomEpisode(Item& item); +bool LinkEpisodeToAnime(Episode& episode, int anime_id); +void StartWatching(Item& item, Episode& episode); +void EndWatching(Item& item, Episode episode); +bool IsUpdateAllowed(Item& item, const Episode& episode, bool ignore_update_time); +void UpdateList(Item& item, Episode& episode); +void AddToQueue(Item& item, const Episode& episode, bool change_status); + +bool GetFansubFilter(int anime_id, std::vector& groups); +bool SetFansubFilter(int anime_id, const std::wstring& group_name); + +std::wstring GetImagePath(int anime_id = -1); + +void GetUpcomingTitles(std::vector& anime_ids); + +bool IsInsideRootFolders(const std::wstring& path); + +int GetEpisodeHigh(const std::wstring& episode_number); +int GetEpisodeLow(const std::wstring& episode_number); +bool IsAllEpisodesAvailable(const Item& item); +bool IsEpisodeRange(const std::wstring& episode_number); +bool IsValidEpisode(int episode, int total); +bool IsValidEpisode(int episode, int watched, int total); +std::wstring JoinEpisodeNumbers(const std::vector& input); +void SplitEpisodeNumbers(const std::wstring& input, std::vector& output); +int EstimateEpisodeCount(const Item& item); +void ChangeEpisode(int anime_id, int value); +void DecrementEpisode(int anime_id); +void IncrementEpisode(int anime_id); + +std::wstring TranslateMyStatus(int value, bool add_count); +std::wstring TranslateNumber(int value, const std::wstring& default_char = L"-"); +std::wstring TranslateStatus(int value); +std::wstring TranslateType(int value); + +int TranslateResolution(const std::wstring& str, bool return_validity = false); + +//////////////////////////////////////////////////////////////////////////////// + +class Season : public base::Comparable { +public: + Season(); + ~Season() {} + + Season& operator = (const Season& season); + + enum Name { + kUnknown, + kWinter, + kSpring, + kSummer, + kFall + }; + + Name name; + unsigned short year; + +private: + base::CompareResult Compare(const Season& season) const; +}; + +bool IsValidDate(const Date& date); +void GetSeasonInterval(const std::wstring& season, Date& date_start, Date& date_end); +std::wstring TranslateDate(const Date& date); +Season TranslateDateToSeason(const Date& date); +std::wstring TranslateDateToSeasonString(const Date& date); +std::wstring TranslateSeasonToMonths(const std::wstring& season); + +} // namespace anime + +#endif // TAIGA_LIBRARY_ANIME_UTIL_H \ No newline at end of file diff --git a/src/library/anime_util_time.cpp b/src/library/anime_util_time.cpp new file mode 100644 index 000000000..179945b76 --- /dev/null +++ b/src/library/anime_util_time.cpp @@ -0,0 +1,179 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include + +#include "base/string.h" +#include "base/time.h" +#include "library/anime_util.h" + +namespace anime { + +Season::Season() + : name(kUnknown), year(0) { +} + +Season& Season::operator=(const Season& season) { + name = season.name; + year = season.year; + + return *this; +} + +base::CompareResult Season::Compare(const Season& season) const { + if (year != season.year) { + if (year == 0) + return base::kGreaterThan; + if (season.year == 0) + return base::kLessThan; + return year < season.year ? base::kLessThan : base::kGreaterThan; + } + + if (name != season.name) { + if (name == Season::kUnknown) + return base::kGreaterThan; + if (season.name == Season::kUnknown) + return base::kLessThan; + return name < season.name ? base::kLessThan : base::kGreaterThan; + } + + return base::kEqualTo; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool IsValidDate(const Date& date) { + return date.year > 0; +} + +void GetSeasonInterval(const std::wstring& season, Date& date_start, + Date& date_end) { + std::map> interval; + interval[L"Spring"] = std::make_pair(3, 5); + interval[L"Summer"] = std::make_pair(6, 8); + interval[L"Fall"] = std::make_pair(9, 11); + interval[L"Winter"] = std::make_pair(12, 2); + + const int days_in_months[] = + {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + + std::vector season_year; + Split(season, L" ", season_year); + + date_start.year = ToInt(season_year.at(1)); + date_start.month = interval[season_year.at(0)].first; + date_start.day = 1; + + if (season_year.at(0) == L"Winter") + date_start.year--; + + date_end.year = ToInt(season_year.at(1)); + date_end.month = interval[season_year.at(0)].second; + date_end.day = days_in_months[date_end.month - 1]; +} + +std::wstring TranslateDate(const Date& date) { + if (!IsValidDate(date)) + return L"?"; + + std::wstring result; + + if (date.month > 0 && date.month <= 12) { + const wchar_t* months[] = { + L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", + L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" + }; + result += months[date.month - 1]; + result += L" "; + } + if (date.day > 0) + result += ToWstr(date.day) + L", "; + result += ToWstr(date.year); + + return result; +} + +Season TranslateDateToSeason(const Date& date) { + Season season; + + season.year = date.year; + + if (date.month == 0) { + season.name = Season::kUnknown; + } else if (date.month < 3) { // Jan-Feb + season.name = Season::kWinter; + } else if (date.month < 6) { // Mar-May + season.name = Season::kSpring; + } else if (date.month < 9) { // Jun-Aug + season.name = Season::kSummer; + } else if (date.month < 12) { // Sep-Nov + season.name = Season::kFall; + } else { // Dec + season.name = Season::kWinter; + season.year++; + } + + return season; +} + +std::wstring TranslateDateToSeasonString(const Date& date) { + if (!IsValidDate(date)) + return L"Unknown"; + + Season season = TranslateDateToSeason(date); + std::wstring name; + + switch (season.name) { + case Season::kUnknown: + name = L"Unknown"; + break; + case Season::kWinter: + name = L"Winter"; + break; + case Season::kSpring: + name = L"Spring"; + break; + case Season::kSummer: + name = L"Summer"; + break; + case Season::kFall: + name = L"Fall"; + break; + } + + return name + L" " + ToWstr(season.year); +} + +std::wstring TranslateSeasonToMonths(const std::wstring& season) { + const wchar_t* months[] = { + L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", + L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" + }; + + Date date_start, date_end; + GetSeasonInterval(season, date_start, date_end); + + std::wstring result = months[date_start.month - 1]; + result += L" " + ToWstr(date_start.year) + L" to "; + result += months[date_end.month - 1]; + result += L" " + ToWstr(date_end.year); + + return result; +} + +} // namespace anime \ No newline at end of file diff --git a/src/library/discover.cpp b/src/library/discover.cpp new file mode 100644 index 000000000..34e565dba --- /dev/null +++ b/src/library/discover.cpp @@ -0,0 +1,171 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include + +#include "base/foreach.h" +#include "base/log.h" +#include "base/string.h" +#include "base/xml.h" +#include "library/anime_db.h" +#include "library/anime_item.h" +#include "library/anime_util.h" +#include "library/discover.h" +#include "sync/manager.h" +#include "sync/service.h" +#include "taiga/path.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" + +library::SeasonDatabase SeasonDatabase; + +namespace library { + +bool SeasonDatabase::Load(std::wstring file) { + items.clear(); + + xml_document document; + std::wstring path = taiga::GetPath(taiga::kPathDatabaseSeason) + file; + xml_parse_result parse_result = document.load_file(path.c_str()); + + if (parse_result.status != pugi::status_ok && + parse_result.status != pugi::status_file_not_found) { + MessageBox(nullptr, L"Could not read season data.", path.c_str(), + MB_OK | MB_ICONERROR); + return false; + } + + xml_node season_node = document.child(L"season"); + + name = XmlReadStrValue(season_node.child(L"info"), L"name"); + time_t modified = _wtoi64(XmlReadStrValue(season_node.child(L"info"), + L"modified").c_str()); + + foreach_xmlnode_(node, season_node, L"anime") { + std::map id_map; + + foreach_xmlnode_(id_node, node, L"id") { + std::wstring id = id_node.child_value(); + std::wstring name = id_node.attribute(L"name").as_string(); + enum_t service_id = ServiceManager.GetServiceIdByName(name); + id_map[service_id] = id; + } + + int anime_id = anime::ID_UNKNOWN; + anime::Item* anime_item = nullptr; + + foreach_(it, id_map) { + anime_item = AnimeDatabase.FindItem(it->second, it->first); + if (anime_item) + break; + } + + if (anime_item && anime_item->GetLastModified() >= modified) { + anime_id = anime_item->GetId(); + } else { + auto current_service_id = taiga::GetCurrentServiceId(); + if (id_map[current_service_id].empty()) { + LOG(LevelDebug, name + L" - No ID for current service: " + + XmlReadStrValue(node, L"title")); + continue; + } + + anime::Item item; + item.SetSource(sync::kMyAnimeList); + foreach_(it, id_map) + item.SetId(it->second, it->first); + item.SetLastModified(modified); + item.SetTitle(XmlReadStrValue(node, L"title")); + item.SetType(XmlReadIntValue(node, L"type")); + item.SetImageUrl(XmlReadStrValue(node, L"image")); + item.SetProducers(XmlReadStrValue(node, L"producers")); + anime_id = AnimeDatabase.UpdateItem(item); + } + + items.push_back(anime_id); + } + + return true; +} + +bool SeasonDatabase::IsRefreshRequired() { + int count = 0; + bool required = false; + + foreach_(it, items) { + int anime_id = *it; + auto anime_item = AnimeDatabase.FindItem(anime_id); + if (anime_item) { + const Date& date_start = anime_item->GetDateStart(); + if (!anime::IsValidDate(date_start) || anime_item->GetSynopsis().empty()) + count++; + } + if (count > 20) { + required = true; + break; + } + } + + return required; +} + +void SeasonDatabase::Review(bool hide_nsfw) { + Date date_start, date_end; + anime::GetSeasonInterval(name, date_start, date_end); + + // Check for invalid items + for (size_t i = 0; i < items.size(); i++) { + int anime_id = items.at(i); + auto anime_item = AnimeDatabase.FindItem(anime_id); + if (anime_item) { + bool invalid = false; + // Airing date must be within the interval + const Date& anime_start = anime_item->GetDateStart(); + if (anime::IsValidDate(anime_start)) + if (anime_start < date_start || anime_start > date_end) + invalid = true; + // Filter by age rating + if (hide_nsfw && IsNsfw(*anime_item)) + invalid = true; + if (invalid) { + items.erase(items.begin() + i--); + LOG(LevelDebug, L"Removed item: \"" + anime_item->GetTitle() + + L"\" (" + std::wstring(anime_start) + L")"); + } + } + } + + // Check for missing items + foreach_(it, AnimeDatabase.items) { + if (std::find(items.begin(), items.end(), it->second.GetId()) != items.end()) + continue; + // Filter by age rating + if (hide_nsfw && IsNsfw(it->second)) + continue; + // Airing date must be within the interval + const Date& anime_start = it->second.GetDateStart(); + if (anime_start.year && anime_start.month && + anime_start >= date_start && anime_start <= date_end) { + items.push_back(it->second.GetId()); + LOG(LevelDebug, L"Added item: \"" + it->second.GetTitle() + + L"\" (" + std::wstring(anime_start) + L")"); + } + } +} + +} // namespace library \ No newline at end of file diff --git a/src/library/discover.h b/src/library/discover.h new file mode 100644 index 000000000..0d38a8a21 --- /dev/null +++ b/src/library/discover.h @@ -0,0 +1,51 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_LIBRARY_DISCOVER_H +#define TAIGA_LIBRARY_DISCOVER_H + +#include + +namespace library { + +class SeasonDatabase { +public: + // Loads season data from db\season\.xml, returns false if no such + // file exists. + bool Load(std::wstring file); + + // Checkes if a significant portion of season data is empty and requires + // refreshing. + bool IsRefreshRequired(); + + // Improves season data by excluding invalid items (i.e. postpones series) and + // adding missing ones from the anime database. + void Review(bool hide_nsfw = true); + + // Only IDs are stored here, actual info is kept in anime::Database. + std::vector items; + + // Season name (e.g. "Spring 2012") + std::wstring name; +}; + +} // namespace library + +extern library::SeasonDatabase SeasonDatabase; + +#endif // TAIGA_LIBRARY_DISCOVER_H \ No newline at end of file diff --git a/src/library/history.cpp b/src/library/history.cpp new file mode 100644 index 000000000..56f7ad28b --- /dev/null +++ b/src/library/history.cpp @@ -0,0 +1,448 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "base/log.h" +#include "base/string.h" +#include "base/xml.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "library/history.h" +#include "sync/sync.h" +#include "taiga/announce.h" +#include "taiga/path.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" +#include "track/search.h" +#include "ui/ui.h" + +class ConfirmationQueue ConfirmationQueue; +class History History; + +HistoryItem::HistoryItem() + : anime_id(anime::ID_UNKNOWN), + enabled(true), + mode(0) { +} + +HistoryQueue::HistoryQueue() + : index(0), + history(nullptr), + updating(false) { +} + +void HistoryQueue::Add(HistoryItem& item, bool save) { + auto anime = AnimeDatabase.FindItem(item.anime_id); + + // Add to user list + if (anime && !anime->IsInList()) + if (item.mode != taiga::kHttpServiceDeleteLibraryEntry) + anime->AddtoUserList(); + + // Validate values + if (anime && anime->IsInList()) { + if (item.episode) + if (anime->GetMyLastWatchedEpisode() == *item.episode || *item.episode < 0) + item.episode.Reset(); + if (item.score) + if (anime->GetMyScore() == *item.score || *item.score < 0 || *item.score > 10) + item.score.Reset(); + if (item.status) + if (anime->GetMyStatus() == *item.status || *item.status < anime::kMyStatusFirst || *item.status >= anime::kMyStatusLast) + item.status.Reset(); + if (item.enable_rewatching) + if (anime->GetMyRewatching() == *item.enable_rewatching) + item.enable_rewatching.Reset(); + if (item.tags) + if (anime->GetMyTags() == *item.tags) + item.tags.Reset(); + if (item.date_start) + if (anime->GetMyDateStart() == *item.date_start) + item.date_start.Reset(); + if (item.date_finish) + if (anime->GetMyDateEnd() == *item.date_finish) + item.date_finish.Reset(); + } + switch (item.mode) { + case taiga::kHttpServiceUpdateLibraryEntry: + if (!item.episode && + !item.score && + !item.status && + !item.enable_rewatching && + !item.tags && + !item.date_start && + !item.date_finish) + return; + break; + } + + // Edit previous item with the same ID... + bool add_new_item = true; + if (!History.queue.updating) { + foreach_r_(it, items) { + if (it->anime_id == item.anime_id && it->enabled) { + if (it->mode != taiga::kHttpServiceAddLibraryEntry && + it->mode != taiga::kHttpServiceDeleteLibraryEntry) { + if (!item.episode || (!it->episode && it == items.rbegin())) { + if (item.episode) + it->episode = *item.episode; + if (item.score) + it->score = *item.score; + if (item.status) + it->status = *item.status; + if (item.enable_rewatching) + it->enable_rewatching = *item.enable_rewatching; + if (item.tags) + it->tags = *item.tags; + if (item.date_start) + it->date_start = *item.date_start; + if (item.date_finish) + it->date_finish = *item.date_finish; + add_new_item = false; + } + if (!add_new_item) { + it->mode = taiga::kHttpServiceUpdateLibraryEntry; + it->time = (std::wstring)GetDate() + L" " + GetTime(); + } + break; + } + } + } + } + // ...or add a new one + if (add_new_item) { + if (item.time.empty()) + item.time = (std::wstring)GetDate() + L" " + GetTime(); + items.push_back(item); + } + + if (anime && save) { + // Save + history->Save(); + + // Announce + if (Taiga.logged_in && item.episode) { + anime::Episode episode; + episode.anime_id = anime->GetId(); + episode.number = ToWstr(*item.episode); + Taiga.play_status = taiga::kPlayStatusUpdated; + Announcer.Do(taiga::kAnnounceToHttp | taiga::kAnnounceToTwitter, &episode); + } + + // Check new episode + if (item.episode) { + anime->SetNextEpisodePath(L""); + ScanAvailableEpisodesQuick(anime->GetId()); + } + + ui::OnHistoryAddItem(item); + + // Update + Check(); + } +} + +void HistoryQueue::Check(bool automatic) { + // Check + if (items.empty()) { + return; + } + if (!items[index].enabled) { + LOG(LevelDebug, L"Item is disabled, removing..."); + Remove(index, true, true, false); + Check(); + return; + } + if (!Taiga.logged_in) { + items[index].reason = L"Not logged in"; + return; + } + if (automatic && !Settings.GetBool(taiga::kApp_Option_EnableSync)) { + items[index].reason = L"Synchronization is disabled"; + return; + } + + // Compare ID with anime list + auto anime_item = AnimeDatabase.FindItem(items[index].anime_id); + if (!anime_item) { + LOG(LevelWarning, L"Item not found in list, removing... ID: " + + ToWstr(items[index].anime_id)); + Remove(index, true, true, false); + Check(); + return; + } + + // Update + History.queue.updating = true; + ui::ChangeStatusText(L"Updating list... (" + anime_item->GetTitle() + L")"); + AnimeValues* anime_values = static_cast(&items[index]); + sync::UpdateLibraryEntry(*anime_values, items[index].anime_id, + static_cast(items[index].mode)); +} + +void HistoryQueue::Clear(bool save) { + items.clear(); + index = 0; + + ui::OnHistoryChange(); + + if (save) + history->Save(); +} + +HistoryItem* HistoryQueue::FindItem(int anime_id, int search_mode) { + for (auto it = items.rbegin(); it != items.rend(); ++it) { + if (it->anime_id == anime_id && it->enabled) { + switch (search_mode) { + // Date + case kQueueSearchDateStart: + if (it->date_start) + return &(*it); + break; + case kQueueSearchDateEnd: + if (it->date_finish) + return &(*it); + break; + // Episode + case kQueueSearchEpisode: + if (it->episode) + return &(*it); + break; + // Re-watching + case kQueueSearchRewatching: + if (it->enable_rewatching) + return &(*it); + break; + // Score + case kQueueSearchScore: + if (it->score) + return &(*it); + break; + // Status + case kQueueSearchStatus: + if (it->status) + return &(*it); + break; + // Tags + case kQueueSearchTags: + if (it->tags) + return &(*it); + break; + // Default + default: + return &(*it); + } + } + } + return nullptr; +} + +HistoryItem* HistoryQueue::GetCurrentItem() { + if (!items.empty()) + return &items.at(index); + + return nullptr; +} + +int HistoryQueue::GetItemCount() { + int count = 0; + + for (auto it = items.begin(); it != items.end(); ++it) + if (it->enabled) + count++; + + return count; +} + +void HistoryQueue::Remove(int index, bool save, bool refresh, bool to_history) { + if (index == -1) + index = this->index; + + if (index < static_cast(items.size())) { + auto history_item = items.begin() + index; + + if (to_history && history_item->episode) { + history->items.push_back(*history_item); + if (history->limit > 0 && + static_cast(history->items.size()) > history->limit) { + history->items.erase(history->items.begin()); + } + } + + items.erase(history_item); + + if (refresh) + ui::OnHistoryChange(); + } + + if (save) + history->Save(); +} + +void HistoryQueue::RemoveDisabled(bool save, bool refresh) { + bool needs_refresh = false; + + for (size_t i = 0; i < items.size(); i++) { + if (!items.at(i).enabled) { + items.erase(items.begin() + i); + needs_refresh = true; + i--; + } + } + + if (refresh && needs_refresh) + ui::OnHistoryChange(); + + if (save) + history->Save(); +} + +//////////////////////////////////////////////////////////////////////////////// + +History::History() + : limit(0) { // Limit of history items (0 for unlimited) + queue.history = this; +} + +void History::Clear(bool save) { + items.clear(); + + ui::OnHistoryChange(); + + if (save) + Save(); +} + +bool History::Load() { + items.clear(); + queue.items.clear(); + + xml_document document; + std::wstring path = taiga::GetPath(taiga::kPathUserHistory); + xml_parse_result parse_result = document.load_file(path.c_str()); + + if (parse_result.status != pugi::status_ok) + return false; + + // Items + xml_node node_items = document.child(L"history").child(L"items"); + foreach_xmlnode_(item, node_items, L"item") { + HistoryItem history_item; + history_item.anime_id = item.attribute(L"anime_id").as_int(anime::ID_NOTINLIST); + history_item.episode = item.attribute(L"episode").as_int(); + history_item.time = item.attribute(L"time").value(); + items.push_back(history_item); + } + // Queue events + xml_node node_queue = document.child(L"history").child(L"queue"); + foreach_xmlnode_(item, node_queue, L"item") { + HistoryItem history_item; + history_item.anime_id = item.attribute(L"anime_id").as_int(anime::ID_NOTINLIST); + history_item.mode = item.attribute(L"mode").as_int(); + history_item.time = item.attribute(L"time").value(); + #define READ_ATTRIBUTE_INT(x, y) \ + if (!item.attribute(y).empty()) x = item.attribute(y).as_int(); + #define READ_ATTRIBUTE_STR(x, y) \ + if (!item.attribute(y).empty()) x = item.attribute(y).as_string(); + #define READ_ATTRIBUTE_DATE(x, y) \ + if (!item.attribute(y).empty()) x = (Date)item.attribute(y).as_string(); + READ_ATTRIBUTE_INT(history_item.episode, L"episode"); + READ_ATTRIBUTE_INT(history_item.score, L"score"); + READ_ATTRIBUTE_INT(history_item.status, L"status"); + READ_ATTRIBUTE_INT(history_item.enable_rewatching, L"enable_rewatching"); + READ_ATTRIBUTE_STR(history_item.tags, L"tags"); + READ_ATTRIBUTE_DATE(history_item.date_start, L"date_start"); + READ_ATTRIBUTE_DATE(history_item.date_finish, L"date_finish"); + #undef READ_ATTRIBUTE_DATE + #undef READ_ATTRIBUTE_STR + #undef READ_ATTRIBUTE_INT + queue.Add(history_item, false); + } + + return true; +} + +bool History::Save() { + xml_document document; + std::wstring path = taiga::GetPath(taiga::kPathUserHistory); + xml_node node_history = document.append_child(L"history"); + + // Write items + xml_node node_items = node_history.append_child(L"items"); + foreach_(it, items) { + xml_node node_item = node_items.append_child(L"item"); + node_item.append_attribute(L"anime_id") = it->anime_id; + node_item.append_attribute(L"episode") = *it->episode; + node_item.append_attribute(L"time") = it->time.c_str(); + } + // Write queue + xml_node node_queue = node_history.append_child(L"queue"); + foreach_(it, queue.items) { + xml_node node_item = node_queue.append_child(L"item"); + #define APPEND_ATTRIBUTE_INT(x, y) \ + if (y) node_item.append_attribute(x) = *y; + #define APPEND_ATTRIBUTE_STR(x, y) \ + if (y) node_item.append_attribute(x) = (*y).c_str(); + #define APPEND_ATTRIBUTE_DATE(x, y) \ + if (y) node_item.append_attribute(x) = std::wstring(*y).c_str(); + node_item.append_attribute(L"anime_id") = it->anime_id; + node_item.append_attribute(L"mode") = it->mode; + node_item.append_attribute(L"time") = it->time.c_str(); + APPEND_ATTRIBUTE_INT(L"episode", it->episode); + APPEND_ATTRIBUTE_INT(L"score", it->score); + APPEND_ATTRIBUTE_INT(L"status", it->status); + APPEND_ATTRIBUTE_INT(L"enable_rewatching", it->enable_rewatching); + APPEND_ATTRIBUTE_STR(L"tags", it->tags); + APPEND_ATTRIBUTE_DATE(L"date_start", it->date_start); + APPEND_ATTRIBUTE_DATE(L"date_finish", it->date_finish); + #undef APPEND_ATTRIBUTE_DATE + #undef APPEND_ATTRIBUTE_STR + #undef APPEND_ATTRIBUTE_INT + } + + return XmlWriteDocumentToFile(document, path); +} + +//////////////////////////////////////////////////////////////////////////////// + +ConfirmationQueue::ConfirmationQueue() + : in_process_(false) { +} + +void ConfirmationQueue::Add(const anime::Episode& episode) { + queue_.push(episode); +} + +void ConfirmationQueue::Process() { + if (in_process_) + return; + in_process_ = true; + + while (!queue_.empty()) { + anime::Episode& episode = queue_.front(); + int choice = ui::OnHistoryProcessConfirmationQueue(episode); + if (choice != IDNO) { + bool change_status = (choice == IDCANCEL); + auto anime_item = AnimeDatabase.FindItem(episode.anime_id); + AddToQueue(*anime_item, episode, change_status); + } + queue_.pop(); + } + + in_process_ = false; +} \ No newline at end of file diff --git a/history.h b/src/library/history.h similarity index 54% rename from history.h rename to src/library/history.h index a5cd17991..6fdd7e1d7 100644 --- a/history.h +++ b/src/library/history.h @@ -1,103 +1,115 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef HISTORY_H -#define HISTORY_H - -#include "std.h" - -#include - -#include "anime_episode.h" -#include "myanimelist.h" - -// ============================================================================= - -enum EventSearchMode { - EVENT_SEARCH_DATE_START = 1, - EVENT_SEARCH_DATE_END, - EVENT_SEARCH_EPISODE, - EVENT_SEARCH_REWATCH, - EVENT_SEARCH_SCORE, - EVENT_SEARCH_STATUS, - EVENT_SEARCH_TAGS -}; - -class History; - -class EventItem : public mal::AnimeValues { -public: - EventItem(); - virtual ~EventItem() {} - - bool enabled; - int anime_id, mode; - wstring reason, time; -}; - -class EventQueue { -public: - EventQueue(); - virtual ~EventQueue() {} - - void Add(EventItem& item, bool save = true); - void Check(bool automatic = true); - void Clear(bool save = true); - EventItem* FindItem(int anime_id, int search_mode = 0); - EventItem* GetCurrentItem(); - int GetItemCount(); - void Remove(int index = -1, bool save = true, bool refresh = true, bool to_history = true); - void RemoveDisabled(bool save = true, bool refresh = true); - - size_t index; - vector items; - History* history; - bool updating; -}; - -class History { -public: - History(); - virtual ~History() {} - - bool Load(); - bool Save(); - - vector items; - EventQueue queue; - int limit; -}; - -extern class History History; - -class ConfirmationQueue { -public: - ConfirmationQueue(); - virtual ~ConfirmationQueue() {} - - void Add(const anime::Episode& episode); - void Process(); - -private: - bool in_process; - std::queue queue_; -}; - -extern class ConfirmationQueue ConfirmationQueue; - -#endif // HISTORY_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_LIBRARY_HISTORY_H +#define TAIGA_LIBRARY_HISTORY_H + +#include +#include +#include + +#include "base/optional.h" +#include "base/time.h" +#include "library/anime_episode.h" + +enum QueueSearchMode { + kQueueSearchDateStart = 1, + kQueueSearchDateEnd, + kQueueSearchEpisode, + kQueueSearchRewatching, + kQueueSearchScore, + kQueueSearchStatus, + kQueueSearchTags +}; + +class AnimeValues { +public: + Optional episode; + Optional status; + Optional score; + Optional date_start; + Optional date_finish; + Optional enable_rewatching; + Optional tags; +}; + +class HistoryItem : public AnimeValues { +public: + HistoryItem(); + virtual ~HistoryItem() {} + + bool enabled; + int anime_id; + int mode; + std::wstring reason; + std::wstring time; +}; + +class History; + +class HistoryQueue { +public: + HistoryQueue(); + ~HistoryQueue() {} + + void Add(HistoryItem& item, bool save = true); + void Check(bool automatic = true); + void Clear(bool save = true); + HistoryItem* FindItem(int anime_id, int search_mode = 0); + HistoryItem* GetCurrentItem(); + int GetItemCount(); + void Remove(int index = -1, bool save = true, bool refresh = true, bool to_history = true); + void RemoveDisabled(bool save = true, bool refresh = true); + + size_t index; + std::vector items; + History* history; + bool updating; +}; + +class History { +public: + History(); + ~History() {} + + void Clear(bool save = true); + bool Load(); + bool Save(); + + std::vector items; + HistoryQueue queue; + int limit; +}; + +class ConfirmationQueue { +public: + ConfirmationQueue(); + virtual ~ConfirmationQueue() {} + + void Add(const anime::Episode& episode); + void Process(); + +private: + bool in_process_; + std::queue queue_; +}; + +extern class History History; +extern class ConfirmationQueue ConfirmationQueue; + +#endif // TAIGA_LIBRARY_HISTORY_H \ No newline at end of file diff --git a/src/library/metadata.cpp b/src/library/metadata.cpp new file mode 100644 index 000000000..a89a07b6b --- /dev/null +++ b/src/library/metadata.cpp @@ -0,0 +1,39 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "metadata.h" + +namespace library { + +Title::Title() + : type(kTitleTypeSynonym) { +} + +Title::Title(TitleType type, const string_t& value) + : type(type), value(value) { +} + +Metadata::Metadata() + : audience(0), + modified(0), + source(0), + status(0), + type(0) { +} + +} // namespace library \ No newline at end of file diff --git a/src/library/metadata.h b/src/library/metadata.h new file mode 100644 index 000000000..1600ba6bd --- /dev/null +++ b/src/library/metadata.h @@ -0,0 +1,71 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_LIBRARY_METADATA_H +#define TAIGA_LIBRARY_METADATA_H + +#include "base/time.h" +#include "base/types.h" + +namespace library { + +enum TitleType { + kTitleTypeUnknown, + kTitleTypeSynonym, + kTitleTypeLangEnglish +}; + +struct Title { + Title(); + Title(TitleType type, const string_t& value); + ~Title() {} + + TitleType type; + string_t value; +}; + +// A generic metadata structure for all kinds of media +struct Metadata { + Metadata(); + ~Metadata() {} + + std::vector uid; + enum_t source; + time_t modified; + + string_t title; + std::vector alternative; + + enum_t type; + enum_t status; + enum_t audience; + + std::vector<unsigned short> extent; + std::vector<Date> date; + + std::vector<string_t> subject; + std::vector<string_t> creator; + std::vector<string_t> resource; + std::vector<string_t> community; + + string_t description; +}; + +} // namespace library + +#endif // TAIGA_LIBRARY_METADATA_H \ No newline at end of file diff --git a/src/library/resource.cpp b/src/library/resource.cpp new file mode 100644 index 000000000..a112381b9 --- /dev/null +++ b/src/library/resource.cpp @@ -0,0 +1,110 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "base/file.h" +#include "base/foreach.h" +#include "library/anime.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "library/discover.h" +#include "library/resource.h" +#include "sync/sync.h" +#include "taiga/path.h" +#include "ui/dlg/dlg_anime_info.h" +#include "ui/dlg/dlg_season.h" + +anime::ImageDatabase ImageDatabase; + +namespace anime { + +bool ImageDatabase::Load(int anime_id, bool load, bool download) { + if (anime_id <= anime::ID_UNKNOWN) + return false; + + if (items_.find(anime_id) != items_.end()) { + if (items_[anime_id].data > anime::ID_UNKNOWN) { + return true; + } else if (!load) { + return false; + } + } + + if (items_[anime_id].Load(anime::GetImagePath(anime_id))) { + items_[anime_id].data = anime_id; + if (download) { + // Refresh if current file is too old + auto anime_item = AnimeDatabase.FindItem(anime_id); + if (anime_item->GetAiringStatus() != kFinishedAiring) { + // Check last modified date (>= 7 days) + if (GetFileAge(anime::GetImagePath(anime_id)) / (60 * 60 * 24) >= 7) { + sync::DownloadImage(anime_id, anime_item->GetImageUrl()); + } + } + } + return true; + } else { + items_[anime_id].data = -1; + } + + if (download) { + auto anime_item = AnimeDatabase.FindItem(anime_id); + sync::DownloadImage(anime_id, anime_item->GetImageUrl()); + } + + return false; +} + +void ImageDatabase::FreeMemory() { + foreach_(it, ::AnimeDatabase.items) { + bool erase = true; + int anime_id = it->first; + + if (items_.find(anime_id) == items_.end()) + continue; + + if (ui::DlgAnime.GetCurrentId() == anime_id || + ui::DlgNowPlaying.GetCurrentId() == anime_id) + erase = false; + + if (!SeasonDatabase.items.empty()) + if (std::find(SeasonDatabase.items.begin(), SeasonDatabase.items.end(), + anime_id) != SeasonDatabase.items.end()) + if (ui::DlgSeason.IsVisible()) + erase = false; + + if (erase) + items_.erase(anime_id); + } +} + +void ImageDatabase::Clear() { + items_.clear(); + + std::wstring path = taiga::GetPath(taiga::kPathDatabaseImage); + DeleteFolder(path); +} + +base::Image* ImageDatabase::GetImage(int anime_id) { + if (items_.find(anime_id) != items_.end()) + if (items_[anime_id].data > 0) + return &items_[anime_id]; + + return nullptr; +} + +} // namespace anime \ No newline at end of file diff --git a/src/library/resource.h b/src/library/resource.h new file mode 100644 index 000000000..c778dcdaa --- /dev/null +++ b/src/library/resource.h @@ -0,0 +1,51 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef TAIGA_LIBRARY_RESOURCE_H +#define TAIGA_LIBRARY_RESOURCE_H + +#include <map> + +#include "base/gfx.h" + +namespace anime { + +class ImageDatabase { +public: + ImageDatabase() {} + virtual ~ImageDatabase() {} + + // Loads a picture into memory, downloads a new file if requested. + bool Load(int anime_id, bool load, bool download); + + // Releases image data from memory if an image is not in sight. + void FreeMemory(); + void Clear(); + + // Returns a pointer to requested image if available. + base::Image* GetImage(int anime_id); + +private: + std::map<int, base::Image> items_; +}; + +} // namespace anime + +extern anime::ImageDatabase ImageDatabase; + +#endif // TAIGA_LIBRARY_RESOURCE_H \ No newline at end of file diff --git a/main.cpp b/src/main.cpp similarity index 67% rename from main.cpp rename to src/main.cpp index cfd982730..a25e1aedc 100644 --- a/main.cpp +++ b/src/main.cpp @@ -1,27 +1,23 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "std.h" - -#include "taiga.h" - -// ============================================================================= - -int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { - return Taiga.Run(); +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "taiga/taiga.h" + +int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { + return Taiga.Run(); } \ No newline at end of file diff --git a/src/sync/hummingbird.cpp b/src/sync/hummingbird.cpp new file mode 100644 index 000000000..999036ec5 --- /dev/null +++ b/src/sync/hummingbird.cpp @@ -0,0 +1,382 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "base/http.h" +#include "base/json.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/anime_item.h" +#include "sync/hummingbird.h" +#include "sync/hummingbird_types.h" +#include "sync/hummingbird_util.h" + +namespace sync { +namespace hummingbird { + +Service::Service() { + host_ = L"hummingbird.me/api/v1"; + + id_ = kHummingbird; + canonical_name_ = L"hummingbird"; + name_ = L"Hummingbird"; +} + +//////////////////////////////////////////////////////////////////////////////// + +void Service::BuildRequest(Request& request, HttpRequest& http_request) { + http_request.url.host = host_; + + // Hummingbird is supposed to return a JSON response for each and every + // request, so that's what we expect from it + http_request.header[L"Accept"] = L"application/json"; + http_request.header[L"Accept-Charset"] = L"utf-8"; + + // kAuthenticateUser method returns the user's authentication token, which + // is to be used on all methods that require authentication. + if (RequestNeedsAuthentication(request.type)) + http_request.url.query[L"auth_token"] = auth_token_; + + switch (request.type) { + case kGetLibraryEntries: + // TODO: Make sure username is available + http_request.header[L"Accept-Encoding"] = L"gzip"; + break; + } + + switch (request.type) { + BUILD_HTTP_REQUEST(kAddLibraryEntry, AddLibraryEntry); + BUILD_HTTP_REQUEST(kAuthenticateUser, AuthenticateUser); + BUILD_HTTP_REQUEST(kDeleteLibraryEntry, DeleteLibraryEntry); + BUILD_HTTP_REQUEST(kGetLibraryEntries, GetLibraryEntries); + BUILD_HTTP_REQUEST(kGetMetadataById, GetMetadataById); + BUILD_HTTP_REQUEST(kGetMetadataByIdV2, GetMetadataByIdV2); + BUILD_HTTP_REQUEST(kSearchTitle, SearchTitle); + BUILD_HTTP_REQUEST(kUpdateLibraryEntry, UpdateLibraryEntry); + } +} + +void Service::HandleResponse(Response& response, HttpResponse& http_response) { + if (RequestSucceeded(response, http_response)) { + switch (response.type) { + HANDLE_HTTP_RESPONSE(kAddLibraryEntry, AddLibraryEntry); + HANDLE_HTTP_RESPONSE(kAuthenticateUser, AuthenticateUser); + HANDLE_HTTP_RESPONSE(kDeleteLibraryEntry, DeleteLibraryEntry); + HANDLE_HTTP_RESPONSE(kGetLibraryEntries, GetLibraryEntries); + HANDLE_HTTP_RESPONSE(kGetMetadataById, GetMetadataById); + HANDLE_HTTP_RESPONSE(kGetMetadataByIdV2, GetMetadataByIdV2); + HANDLE_HTTP_RESPONSE(kSearchTitle, SearchTitle); + HANDLE_HTTP_RESPONSE(kUpdateLibraryEntry, UpdateLibraryEntry); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Request builders + +void Service::AuthenticateUser(Request& request, HttpRequest& http_request) { + http_request.method = L"POST"; + http_request.url.path = L"/users/authenticate"; + + // TODO: Make sure username and password are available + http_request.url.query[L"username"] = + request.data[canonical_name_ + L"-username"]; + http_request.url.query[L"password"] = + request.data[canonical_name_ + L"-password"]; +} + +void Service::GetLibraryEntries(Request& request, HttpRequest& http_request) { + http_request.url.path = + L"/users/" + request.data[canonical_name_ + L"-username"] + L"/library"; + + if (request.data.count(L"status")) + http_request.url.query[L"status"] = + TranslateMyStatusTo(ToInt(request.data[L"status"])); +} + +void Service::GetMetadataById(Request& request, HttpRequest& http_request) { + http_request.url.path = L"/anime/" + request.data[canonical_name_ + L"-id"]; +} + +void Service::GetMetadataByIdV2(Request& request, HttpRequest& http_request) { + // Hummingbird APIv2 is not yet ready, but this method can be called for + // additional data that APIv1 doesn't provide. + http_request.url.host = L"hummingbird.me/api/v2"; + http_request.url.path = L"/anime/" + request.data[canonical_name_ + L"-id"]; +} + +void Service::SearchTitle(Request& request, HttpRequest& http_request) { + // This method is not documented, but otherwise works fine. Note that it will + // return only 5 results at a time. + http_request.url.path = L"/search/anime"; + http_request.url.query[L"query"] = request.data[L"title"]; +} + +void Service::AddLibraryEntry(Request& request, HttpRequest& http_request) { + UpdateLibraryEntry(request, http_request); +} + +void Service::DeleteLibraryEntry(Request& request, HttpRequest& http_request) { + http_request.method = L"POST"; + http_request.url.path = + L"/libraries/" + request.data[canonical_name_ + L"-id"] + L"/remove"; +} + +void Service::UpdateLibraryEntry(Request& request, HttpRequest& http_request) { + http_request.method = L"POST"; + http_request.url.path = + L"/libraries/" + request.data[canonical_name_ + L"-id"]; + + // When this undocumented parameter is included, Hummingbird will return a + // "mal_id" value that identifies the corresponding entry in MyAnimeList, if + // available. + http_request.url.query[L"include_mal_id"] = L"true"; + + if (request.data.count(L"status")) + http_request.url.query[L"status"] = + TranslateMyStatusTo(ToInt(request.data[L"status"])); + if (request.data.count(L"score")) + http_request.url.query[L"rating"] = + TranslateMyRatingTo(ToInt(request.data[L"score"])); + if (request.data.count(L"episode")) + http_request.url.query[L"episodes_watched"] = request.data[L"episode"]; +} + +//////////////////////////////////////////////////////////////////////////////// +// Response handlers + +void Service::AuthenticateUser(Response& response, HttpResponse& http_response) { + auth_token_ = http_response.body; + Trim(auth_token_, L"\"'"); +} + +void Service::GetLibraryEntries(Response& response, HttpResponse& http_response) { + Json::Value root; + + if (!ParseResponseBody(response, http_response, root)) + return; + + for (size_t i = 0; i < root.size(); i++) + ParseLibraryObject(root[i]); +} + +void Service::GetMetadataById(Response& response, HttpResponse& http_response) { + Json::Value root; + + if (!ParseResponseBody(response, http_response, root)) + return; + + ::anime::Item anime_item; + anime_item.SetSource(this->id()); + anime_item.SetId(ToWstr(root["id"].asInt()), this->id()); + anime_item.SetLastModified(time(nullptr)); // current time + + ParseAnimeObject(root, anime_item); + + AnimeDatabase.UpdateItem(anime_item); +} + +void Service::GetMetadataByIdV2(Response& response, HttpResponse& http_response) { + Json::Value root; + + if (!ParseResponseBody(response, http_response, root)) + return; + + ::anime::Item anime_item; + anime_item.SetSource(this->id()); + anime_item.SetId(ToWstr(root["id"].asInt()), this->id()); + anime_item.SetLastModified(time(nullptr)); // current time + + ParseAnimeObjectV2(root, anime_item); + + AnimeDatabase.UpdateItem(anime_item); +} + +void Service::SearchTitle(Response& response, HttpResponse& http_response) { + Json::Value root; + + if (!ParseResponseBody(response, http_response, root)) + return; + + for (size_t i = 0; i < root.size(); i++) { + ::anime::Item anime_item; + anime_item.SetSource(this->id()); + anime_item.SetId(ToWstr(root[i]["id"].asInt()), this->id()); + anime_item.SetLastModified(time(nullptr)); // current time + + ParseAnimeObject(root[i], anime_item); + + int anime_id = AnimeDatabase.UpdateItem(anime_item); + + // We return a list of IDs so that we can display the results afterwards + AppendString(response.data[L"ids"], ToWstr(anime_id), L","); + } +} + +void Service::AddLibraryEntry(Response& response, HttpResponse& http_response) { + // Returns "false" +} + +void Service::DeleteLibraryEntry(Response& response, HttpResponse& http_response) { + // Returns "true" +} + +void Service::UpdateLibraryEntry(Response& response, HttpResponse& http_response) { + Json::Value root; + + if (!ParseResponseBody(response, http_response, root)) + return; + + ParseLibraryObject(root); +} + +//////////////////////////////////////////////////////////////////////////////// + +bool Service::RequestNeedsAuthentication(RequestType request_type) const { + switch (request_type) { + case kAddLibraryEntry: + case kDeleteLibraryEntry: + case kUpdateLibraryEntry: + return true; + } + + return false; +} + +bool Service::RequestSucceeded(Response& response, + const HttpResponse& http_response) { + switch (http_response.code) { + // OK + case 200: + case 201: + return true; + + // Error + default: { + Json::Value root; + Json::Reader reader; + bool parsed = reader.parse(WstrToStr(http_response.body), root); + response.data[L"error"] = name() + L" returned an error: "; + if (parsed) { + response.data[L"error"] += StrToWstr(root["error"].asString()); + } else { + response.data[L"error"] += L"Unknown error (" + + ToWstr(static_cast<int>(http_response.code)) + L")"; + } + return false; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void Service::ParseAnimeObject(Json::Value& value, anime::Item& anime_item) { + anime_item.SetSlug(StrToWstr(value["slug"].asString())); + anime_item.SetAiringStatus(TranslateSeriesStatusFrom(StrToWstr(value["status"].asString()))); + anime_item.SetTitle(StrToWstr(value["title"].asString())); + anime_item.SetSynonyms(StrToWstr(value["alternate_title"].asString())); + anime_item.SetEpisodeCount(value["episode_count"].asInt()); + anime_item.SetImageUrl(StrToWstr(value["cover_image"].asString())); + anime_item.SetSynopsis(StrToWstr(value["synopsis"].asString())); + anime_item.SetType(TranslateSeriesTypeFrom(StrToWstr(value["show_type"].asString()))); + + std::vector<std::wstring> genres; + auto& genres_value = value["genres"]; + for (size_t i = 0; i < genres_value.size(); i++) + genres.push_back(StrToWstr(genres_value[i]["name"].asString())); + + if (!genres.empty()) + anime_item.SetGenres(genres); +} + +void Service::ParseAnimeObjectV2(Json::Value& value, anime::Item& anime_item) { + anime_item.SetSlug(StrToWstr(value["slug"].asString())); + anime_item.SetTitle(StrToWstr(value["canonical_title"].asString())); + anime_item.SetEnglishTitle(StrToWstr(value["english_title"].asString())); + anime_item.SetSynonyms(StrToWstr(value["romaji_title"].asString())); + anime_item.SetSynopsis(StrToWstr(value["synopsis"].asString())); + anime_item.SetImageUrl(StrToWstr(value["poster_image"].asString())); + anime_item.SetType(TranslateSeriesTypeFrom(StrToWstr(value["type"].asString()))); + anime_item.SetDateStart(StrToWstr(value["started_airing"].asString())); + anime_item.SetDateEnd(StrToWstr(value["finished_airing"].asString())); + anime_item.SetScore(TranslateSeriesRatingFrom(value["community_rating"].asFloat())); + anime_item.SetAgeRating(TranslateAgeRatingFrom(StrToWstr(value["age_rating"].asString()))); + anime_item.SetEpisodeCount(value["episode_count"].asInt()); + anime_item.SetEpisodeLength(value["episode_length"].asInt()); + + std::vector<std::wstring> genres; + auto& genres_value = value["genres"]; + for (size_t i = 0; i < genres_value.size(); i++) + genres.push_back(StrToWstr(genres_value[i].asString())); + + if (!genres.empty()) + anime_item.SetGenres(genres); +} + +void Service::ParseLibraryObject(Json::Value& value) { + auto& anime_value = value["anime"]; + auto& rating_value = value["rating"]; + + ::anime::Item anime_item; + anime_item.SetSource(this->id()); + anime_item.SetId(ToWstr(anime_value["id"].asInt()), this->id()); + anime_item.SetLastModified(time(nullptr)); // current time + + int mal_id = value["mal_id"].asInt(); + if (mal_id > 0) + anime_item.SetId(ToWstr(mal_id), sync::kMyAnimeList); + + ParseAnimeObject(anime_value, anime_item); + + anime_item.AddtoUserList(); + anime_item.SetMyLastWatchedEpisode(value["episodes_watched"].asInt()); + anime_item.SetMyStatus(TranslateMyStatusFrom(StrToWstr(value["status"].asString()))); + anime_item.SetMyRewatching(value["rewatching"].asBool()); + anime_item.SetMyScore(TranslateMyRatingFrom(StrToWstr(rating_value["value"].asString()), + StrToWstr(rating_value["type"].asString()))); + + AnimeDatabase.UpdateItem(anime_item); +} + +bool Service::ParseResponseBody(Response& response, HttpResponse& http_response, + Json::Value& root) { + Json::Reader reader; + + if (reader.parse(WstrToStr(http_response.body), root)) + return true; + + switch (response.type) { + case kGetLibraryEntries: + response.data[L"error"] = L"Could not parse the list"; + break; + case kGetMetadataById: + case kGetMetadataByIdV2: + response.data[L"error"] = L"Could not parse the anime object"; + break; + case kSearchTitle: + response.data[L"error"] = L"Could not parse search results"; + break; + case kUpdateLibraryEntry: + response.data[L"error"] = L"Could not parse library entry"; + break; + } + + return false; +} + +} // namespace hummingbird +} // namespace sync \ No newline at end of file diff --git a/src/sync/hummingbird.h b/src/sync/hummingbird.h new file mode 100644 index 000000000..32eb85f1c --- /dev/null +++ b/src/sync/hummingbird.h @@ -0,0 +1,68 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef TAIGA_SYNC_HUMMINGBIRD_H +#define TAIGA_SYNC_HUMMINGBIRD_H + +#include "base/types.h" +#include "sync/hummingbird_types.h" +#include "sync/service.h" + +namespace Json { +class Value; +} + +namespace sync { +namespace hummingbird { + +// API documentation: +// http://www.mashape.com/vikhyat/hummingbird-v1 + +class Service : public sync::Service { +public: + Service(); + ~Service() {} + + void BuildRequest(Request& request, HttpRequest& http_request); + void HandleResponse(Response& response, HttpResponse& http_response); + bool RequestNeedsAuthentication(RequestType request_type) const; + +private: + REQUEST_AND_RESPONSE(AddLibraryEntry); + REQUEST_AND_RESPONSE(AuthenticateUser); + REQUEST_AND_RESPONSE(DeleteLibraryEntry); + REQUEST_AND_RESPONSE(GetLibraryEntries); + REQUEST_AND_RESPONSE(GetMetadataById); + REQUEST_AND_RESPONSE(GetMetadataByIdV2); + REQUEST_AND_RESPONSE(SearchTitle); + REQUEST_AND_RESPONSE(UpdateLibraryEntry); + + bool RequestSucceeded(Response& response, const HttpResponse& http_response); + + void ParseAnimeObject(Json::Value& value, anime::Item& anime_item); + void ParseAnimeObjectV2(Json::Value& value, anime::Item& anime_item); + void ParseLibraryObject(Json::Value& value); + bool ParseResponseBody(Response& response, HttpResponse& http_response, Json::Value& root); + + string_t auth_token_; +}; + +} // namespace hummingbird +} // namespace sync + +#endif // TAIGA_SYNC_HUMMINGBIRD_H \ No newline at end of file diff --git a/src/sync/hummingbird_types.h b/src/sync/hummingbird_types.h new file mode 100644 index 000000000..5d75baf76 --- /dev/null +++ b/src/sync/hummingbird_types.h @@ -0,0 +1,56 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef TAIGA_SYNC_HUMMINGBIRD_TYPES_H +#define TAIGA_SYNC_HUMMINGBIRD_TYPES_H + +namespace sync { +namespace hummingbird { + +enum SeriesStatus { + kCurrentlyAiring = 1, + kFinishedAiring, + kNotYetAired +}; + +enum SeriesType { + kTv = 1, + kMovie, + kOva, + kOna, + kSpecial, + kMusic +}; + +enum MyPrivacy { + kPrivate = 1, + kPublic +}; + +enum MyStatus { + kCurrentlyWatching = 1, + kPlanToWatch, + kCompleted, + kOnHold, + kDropped +}; + +} // namespace hummingbird +} // namespace sync + +#endif // TAIGA_SYNC_HUMMINGBIRD_TYPES_H \ No newline at end of file diff --git a/src/sync/hummingbird_util.cpp b/src/sync/hummingbird_util.cpp new file mode 100644 index 000000000..c150726c6 --- /dev/null +++ b/src/sync/hummingbird_util.cpp @@ -0,0 +1,205 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "base/file.h" +#include "base/log.h" +#include "base/string.h" +#include "library/anime.h" +#include "library/anime_db.h" +#include "sync/hummingbird_util.h" +#include "sync/hummingbird_types.h" +#include "taiga/settings.h" + +namespace sync { +namespace hummingbird { + +int TranslateAgeRatingFrom(const std::wstring& value) { + if (IsEqual(value, L"G")) { + return anime::kAgeRatingG; + } else if (IsEqual(value, L"PG")) { + return anime::kAgeRatingPG; + } else if (IsEqual(value, L"PG13")) { + return anime::kAgeRatingPG13; + } else if (IsEqual(value, L"R17+")) { + return anime::kAgeRatingR17; + } else if (IsEqual(value, L"R18+")) { + return anime::kAgeRatingR18; + } + + LOG(LevelDebug, L"Unknown value: " + value); + return anime::kUnknownAgeRating; +} + +std::wstring TranslateSeriesRatingFrom(float value) { + return ToWstr(static_cast<double>(value) * 2.0, 2); +} + +int TranslateSeriesStatusFrom(int value) { + switch (value) { + case kCurrentlyAiring: return anime::kAiring; + case kFinishedAiring: return anime::kFinishedAiring; + case kNotYetAired: return anime::kNotYetAired; + } + + LOG(LevelWarning, L"Unknown value: " + ToWstr(value)); + return anime::kUnknownStatus; +} + +int TranslateSeriesStatusFrom(const std::wstring& value) { + if (IsEqual(value, L"Currently Airing")) { + return anime::kAiring; + } else if (IsEqual(value, L"Finished Airing")) { + return anime::kFinishedAiring; + } else if (IsEqual(value, L"Not Yet Aired")) { + return anime::kNotYetAired; + } + + LOG(LevelWarning, L"Unknown value: " + value); + return anime::kUnknownStatus; +} + +int TranslateSeriesTypeFrom(int value) { + switch (value) { + case kTv: return anime::kTv; + case kOva: return anime::kOva; + case kMovie: return anime::kMovie; + case kSpecial: return anime::kSpecial; + case kOna: return anime::kOna; + case kMusic: return anime::kMusic; + } + + LOG(LevelWarning, L"Unknown value: " + ToWstr(value)); + return anime::kUnknownType; +} + +int TranslateSeriesTypeFrom(const std::wstring& value) { + if (IsEqual(value, L"TV")) { + return anime::kTv; + } else if (IsEqual(value, L"Movie")) { + return anime::kMovie; + } else if (IsEqual(value, L"OVA")) { + return anime::kOva; + } else if (IsEqual(value, L"ONA")) { + return anime::kOna; + } else if (IsEqual(value, L"Special")) { + return anime::kSpecial; + } else if (IsEqual(value, L"Music")) { + return anime::kMusic; + } + + LOG(LevelWarning, L"Unknown value: " + value); + return anime::kUnknownType; +} + +std::wstring TranslateDateFrom(const std::wstring& value) { + if (value.size() < 10) + return Date(); + + // Get YYYY-MM-DD from YYYY-MM-DDTHH:MM:SS.000Z + return value.substr(0, 10); +} + +int TranslateMyStatusFrom(const std::wstring& value) { + if (IsEqual(value, L"currently-watching")) { + return anime::kWatching; + } else if (IsEqual(value, L"plan-to-watch")) { + return anime::kPlanToWatch; + } else if (IsEqual(value, L"completed")) { + return anime::kCompleted; + } else if (IsEqual(value, L"on-hold")) { + return anime::kOnHold; + } else if (IsEqual(value, L"dropped")) { + return anime::kDropped; + } + + LOG(LevelWarning, L"Unknown value: " + value); + return anime::kNotInList; +} + +int TranslateMyRatingFrom(const std::wstring& value, const std::wstring& type) { + return static_cast<int>(ToDouble(value) * 2.0); +} + +std::wstring TranslateMyRatingTo(int value) { + return ToWstr(static_cast<double>(value) / 2.0, 1); +} + +std::wstring TranslateMyStatusTo(int value) { + switch (value) { + case anime::kWatching: return L"currently-watching"; + case anime::kCompleted: return L"completed"; + case anime::kOnHold: return L"on-hold"; + case anime::kDropped: return L"dropped"; + case anime::kPlanToWatch: return L"plan-to-watch"; + } + + LOG(LevelWarning, L"Unknown value: " + ToWstr(value)); + return L""; +} + +std::wstring TranslateKeyTo(const std::wstring& key) { + if (IsEqual(key, L"episode")) { + return key; + } else if (IsEqual(key, L"status")) { + return key; + } else if (IsEqual(key, L"score")) { + return key; + } else if (IsEqual(key, L"date_start")) { + return key; + } else if (IsEqual(key, L"date_finish")) { + return key; + } else if (IsEqual(key, L"enable_rewatching")) { + return key; + } else if (IsEqual(key, L"tags")) { + return key; + } + + return std::wstring(); +} + +//////////////////////////////////////////////////////////////////////////////// + +std::wstring GetAnimePage(const anime::Item& anime_item) { + return L"http://hummingbird.me/anime/" + anime_item.GetSlug(); +} + +void ViewAnimePage(int anime_id) { + auto anime_item = AnimeDatabase.FindItem(anime_id); + + ExecuteLink(GetAnimePage(*anime_item)); +} + +void ViewDashboard() { + ExecuteLink(L"http://hummingbird.me/dashboard"); +} + +void ViewProfile() { + ExecuteLink(L"http://hummingbird.me/users/" + + Settings[taiga::kSync_Service_Hummingbird_Username]); +} + +void ViewRecommendations() { + ExecuteLink(L"http://hummingbird.me/recommendations"); +} + +void ViewUpcomingAnime() { + ExecuteLink(L"http://hummingbird.me/anime/upcoming"); +} + +} // namespace hummingbird +} // namespace sync \ No newline at end of file diff --git a/src/sync/hummingbird_util.h b/src/sync/hummingbird_util.h new file mode 100644 index 000000000..d81e06597 --- /dev/null +++ b/src/sync/hummingbird_util.h @@ -0,0 +1,54 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef TAIGA_SYNC_HUMMINGBIRD_UTIL_H +#define TAIGA_SYNC_HUMMINGBIRD_UTIL_H + +#include <string> + +namespace anime { +class Item; +} + +namespace sync { +namespace hummingbird { + +int TranslateAgeRatingFrom(const std::wstring& value); +std::wstring TranslateSeriesRatingFrom(float value); +int TranslateSeriesStatusFrom(int value); +int TranslateSeriesStatusFrom(const std::wstring& value); +int TranslateSeriesTypeFrom(int value); +int TranslateSeriesTypeFrom(const std::wstring& value); +std::wstring TranslateDateFrom(const std::wstring& value); +int TranslateMyRatingFrom(const std::wstring& value, const std::wstring& type); +std::wstring TranslateMyRatingTo(int value); +int TranslateMyStatusFrom(const std::wstring& value); +std::wstring TranslateMyStatusTo(int value); +std::wstring TranslateKeyTo(const std::wstring& key); + +std::wstring GetAnimePage(const anime::Item& anime_item); +void ViewAnimePage(int anime_id); +void ViewDashboard(); +void ViewProfile(); +void ViewRecommendations(); +void ViewUpcomingAnime(); + +} // namespace hummingbird +} // namespace sync + +#endif // TAIGA_SYNC_HUMMINGBIRD_UTIL_H \ No newline at end of file diff --git a/src/sync/manager.cpp b/src/sync/manager.cpp new file mode 100644 index 000000000..999fac89c --- /dev/null +++ b/src/sync/manager.cpp @@ -0,0 +1,257 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "base/foreach.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/history.h" +#include "sync/hummingbird.h" +#include "sync/manager.h" +#include "sync/myanimelist.h" +#include "sync/sync.h" +#include "taiga/http.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" +#include "ui/ui.h" + +sync::Manager ServiceManager; + +namespace sync { + +Manager::Manager() { + // Create services + services_[kMyAnimeList].reset(new myanimelist::Service()); + services_[kHummingbird].reset(new hummingbird::Service()); +} + +Manager::~Manager() { + // Services will automatically free themselves +} + +const Service* Manager::service(ServiceId service_id) { + if (services_.count(service_id)) + return services_[service_id].get(); + + return nullptr; +} + +const Service* Manager::service(const string_t& canonical_name) { + foreach_(service, services_) + if (canonical_name == service->second.get()->canonical_name()) + return service->second.get(); + + return nullptr; +} + +ServiceId Manager::GetServiceIdByName(const string_t& canonical_name) { + auto found_service = service(canonical_name); + + if (found_service) + return static_cast<ServiceId>(found_service->id()); + + return kTaiga; +} + +string_t Manager::GetServiceNameById(ServiceId service_id) { + auto found_service = service(service_id); + + if (found_service) + return found_service->canonical_name(); + + return L"taiga"; +} + +//////////////////////////////////////////////////////////////////////////////// + +void Manager::MakeRequest(Request& request) { + foreach_(service, services_) { + if (request.service_id == kAllServices || + request.service_id == service->first) { + // Create a new HTTP request, and store its UID alongside the service + // request until we receive a response + HttpRequest http_request; + requests_.insert(std::make_pair(http_request.uid, request)); + + // Make sure we store the actual service ID + if (request.service_id == kAllServices) + requests_[http_request.uid].service_id = service->first; + + // Let the service build the HTTP request + service->second->BuildRequest(request, http_request); + + // Make the request + ConnectionManager.MakeRequest(http_request, + RequestTypeToClientMode(request.type)); + } + } +} + +void Manager::HandleHttpError(HttpResponse& http_response, string_t error) { + win::Lock lock(critical_section_); + + const Request& request = requests_[http_response.uid]; + + Response response; + response.service_id = request.service_id; + response.type = request.type; + response.data[L"error"] = error; + + HandleError(response, http_response); + + // FIXME: Not thread-safe. Invalidates iterators on other threads. +//requests_.erase(http_response.uid); +} + +void Manager::HandleHttpResponse(HttpResponse& http_response) { + win::Lock lock(critical_section_); + + const Request& request = requests_[http_response.uid]; + + Response response; + response.service_id = request.service_id; + response.type = request.type; + + HandleResponse(response, http_response); + + // FIXME: Not thread-safe. Invalidates iterators on other threads. +//requests_.erase(http_response.uid); +} + +//////////////////////////////////////////////////////////////////////////////// + +void Manager::HandleError(Response& response, HttpResponse& http_response) { + Request& request = requests_[http_response.uid]; + + int anime_id = ::anime::ID_UNKNOWN; + if (request.data.count(L"taiga-id")) + anime_id = ToInt(request.data[L"taiga-id"]); + auto anime_item = AnimeDatabase.FindItem(anime_id); + + switch (response.type) { + case kAuthenticateUser: + Taiga.logged_in = false; + ui::OnLogout(); + break; + case kGetMetadataById: + ui::OnLibraryEntryChangeFailure(anime_id, response.data[L"error"]); + // Try making the other request, even though this one failed + if (response.service_id == kMyAnimeList && anime_item) { + SearchTitle(anime_item->GetTitle(), anime_id); + } else if (response.service_id == kHummingbird && anime_item) { + GetMetadataByIdV2(anime_id); + } + break; + case kGetMetadataByIdV2: + ui::OnLibraryEntryChangeFailure(anime_id, response.data[L"error"]); + break; + case kGetLibraryEntries: + ui::OnLibraryChangeFailure(); + ui::ChangeStatusText(response.data[L"error"]); + break; + case kAddLibraryEntry: + case kDeleteLibraryEntry: + case kUpdateLibraryEntry: + History.queue.updating = false; + ui::OnLibraryUpdateFailure(anime_id, response.data[L"error"]); + break; + default: + ui::ChangeStatusText(response.data[L"error"]); + break; + } +} + +void Manager::HandleResponse(Response& response, HttpResponse& http_response) { + // Let the service do its thing + Service& service = *services_[response.service_id].get(); + service.HandleResponse(response, http_response); + + // Check for error + if (response.data.count(L"error")) { + HandleError(response, http_response); + return; + } + + Request& request = requests_[http_response.uid]; + + int anime_id = ::anime::ID_UNKNOWN; + if (request.data.count(L"taiga-id")) + anime_id = ToInt(request.data[L"taiga-id"]); + auto anime_item = AnimeDatabase.FindItem(anime_id); + + switch (response.type) { + case kAuthenticateUser: { + string_t username = response.data[service.canonical_name() + L"-username"]; + if (response.service_id == kMyAnimeList && !username.empty()) { + // Update settings with the returned value for the correct letter case + Settings.Set(taiga::kSync_Service_Mal_Username, username); + } + Taiga.logged_in = true; + ui::OnLogin(); + Synchronize(); + break; + } + + case kGetMetadataById: { + ui::OnLibraryEntryChange(anime_id); + // We need to make another request, because MyAnimeList doesn't have + // a proper method in its API for metadata retrieval, and the one we use + // doesn't provide us enough information. + if (response.service_id == kMyAnimeList && anime_item) { + SearchTitle(anime_item->GetTitle(), anime_id); + // Hummingbird APIv1 doesn't provide all the information we need, so we + // make an additional call to APIv2. + } else if (response.service_id == kHummingbird && anime_item) { + GetMetadataByIdV2(anime_id); + } + break; + } + case kGetMetadataByIdV2: { + ui::OnLibraryEntryChange(anime_id); + break; + } + + case kSearchTitle: { + if (!anime_item) + ui::ClearStatusText(); + ui::OnLibrarySearchTitle(anime_id, response.data[L"ids"]); + break; + } + + case kGetLibraryEntries: { + AnimeDatabase.SaveList(); + ui::ChangeStatusText(L"Successfully downloaded the list."); + ui::OnLibraryChange(); + break; + } + + case kAddLibraryEntry: + case kDeleteLibraryEntry: + case kUpdateLibraryEntry: { + History.queue.updating = false; + ui::ClearStatusText(); + + auto history_item = History.queue.GetCurrentItem(); + if (history_item) + AnimeDatabase.UpdateItem(*history_item); + + break; + } + } +} + +} // namespace sync \ No newline at end of file diff --git a/src/sync/manager.h b/src/sync/manager.h new file mode 100644 index 000000000..a7515364a --- /dev/null +++ b/src/sync/manager.h @@ -0,0 +1,63 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef TAIGA_SYNC_MANAGER_H +#define TAIGA_SYNC_MANAGER_H + +#include <map> +#include <memory> +#include <string> +#include "service.h" +#include "base/types.h" +#include "taiga/http.h" +#include "win/win_thread.h" + +namespace sync { + +// The service manager handles all the communication between services and the +// application. + +class Manager { +public: + Manager(); + ~Manager(); + + void MakeRequest(Request& request); + void HandleHttpError(HttpResponse& http_response, string_t error); + void HandleHttpResponse(HttpResponse& http_response); + + const Service* service(ServiceId service_id); + const Service* service(const string_t& canonical_name); + + ServiceId GetServiceIdByName(const string_t& canonical_name); + string_t GetServiceNameById(ServiceId service_id); + +private: + void HandleError(Response& response, HttpResponse& http_response); + void HandleResponse(Response& response, HttpResponse& http_response); + + win::CriticalSection critical_section_; + std::map<std::wstring, Request> requests_; + std::map<ServiceId, std::unique_ptr<Service>> services_; +}; + +} // namespace sync + +extern sync::Manager ServiceManager; + +#endif // TAIGA_SYNC_MANAGER_H \ No newline at end of file diff --git a/src/sync/myanimelist.cpp b/src/sync/myanimelist.cpp new file mode 100644 index 000000000..0393d7338 --- /dev/null +++ b/src/sync/myanimelist.cpp @@ -0,0 +1,505 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <set> + +#include "base/base64.h" +#include "base/foreach.h" +#include "base/html.h" +#include "base/http.h" +#include "base/string.h" +#include "base/xml.h" +#include "library/anime_db.h" +#include "library/anime_item.h" +#include "library/anime_util.h" +#include "sync/myanimelist.h" +#include "sync/myanimelist_util.h" + +namespace sync { +namespace myanimelist { + +Service::Service() { + host_ = L"myanimelist.net"; + + id_ = kMyAnimeList; + canonical_name_ = L"myanimelist"; + name_ = L"MyAnimeList"; +} + +//////////////////////////////////////////////////////////////////////////////// + +void Service::BuildRequest(Request& request, HttpRequest& http_request) { + http_request.url.host = host_; + + // This doesn't quite help; MAL returns whatever it pleases + http_request.header[L"Accept"] = L"text/xml, text/*"; + http_request.header[L"Accept-Charset"] = L"utf-8"; + + // Since October 2013, third-party applications need to identify themselves + // with a unique user-agent string that is whitelisted by MAL. Using a generic + // value (e.g. "Mozilla/5.0") or an arbitrary one (e.g. "Taiga/1.0") will + // result in invalid text/html responses, courtesy of Incapsula. + // To get your own whitelisted user-agent string, follow the registration link + // at the official MAL API club page. If, for any reason, you'd like to use + // Taiga's instead, I will appreciate it if you ask beforehand. + http_request.header[L"User-Agent"] = + L"api-taiga-32864c09ef538453b4d8110734ee355b"; + + if (RequestNeedsAuthentication(request.type)) { + // TODO: Make sure username and password are available + http_request.header[L"Authorization"] = L"Basic " + + Base64Encode(request.data[canonical_name_ + L"-username"] + L":" + + request.data[canonical_name_ + L"-password"]); + } + + switch (request.type) { + case kGetLibraryEntries: + // Compressed lists save us a lot of bandwidth and time + // TODO: Make sure username is available + http_request.header[L"Accept-Encoding"] = L"gzip"; + break; + } + + switch (request.type) { + BUILD_HTTP_REQUEST(kAddLibraryEntry, AddLibraryEntry); + BUILD_HTTP_REQUEST(kAuthenticateUser, AuthenticateUser); + BUILD_HTTP_REQUEST(kDeleteLibraryEntry, DeleteLibraryEntry); + BUILD_HTTP_REQUEST(kGetLibraryEntries, GetLibraryEntries); + BUILD_HTTP_REQUEST(kGetMetadataById, GetMetadataById); + BUILD_HTTP_REQUEST(kSearchTitle, SearchTitle); + BUILD_HTTP_REQUEST(kUpdateLibraryEntry, UpdateLibraryEntry); + } +} + +void Service::HandleResponse(Response& response, HttpResponse& http_response) { + if (RequestSucceeded(response, http_response)) { + switch (response.type) { + HANDLE_HTTP_RESPONSE(kAddLibraryEntry, AddLibraryEntry); + HANDLE_HTTP_RESPONSE(kAuthenticateUser, AuthenticateUser); + HANDLE_HTTP_RESPONSE(kDeleteLibraryEntry, DeleteLibraryEntry); + HANDLE_HTTP_RESPONSE(kGetLibraryEntries, GetLibraryEntries); + HANDLE_HTTP_RESPONSE(kGetMetadataById, GetMetadataById); + HANDLE_HTTP_RESPONSE(kSearchTitle, SearchTitle); + HANDLE_HTTP_RESPONSE(kUpdateLibraryEntry, UpdateLibraryEntry); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Request builders + +void Service::AuthenticateUser(Request& request, HttpRequest& http_request) { + http_request.url.path = L"/api/account/verify_credentials.xml"; +} + +void Service::GetLibraryEntries(Request& request, HttpRequest& http_request) { + // malappinfo.php is an undocumented feature of MAL. While it's not a part + // of their official API, it's the easiest way to get user lists. + http_request.url.path = L"/malappinfo.php"; + http_request.url.query[L"u"] = request.data[canonical_name_ + L"-username"]; + // Changing the status parameter to some other value such as "1" or "watching" + // doesn't seem to make any difference. + http_request.url.query[L"status"] = L"all"; +} + +void Service::GetMetadataById(Request& request, HttpRequest& http_request) { + // As MAL API doesn't have a method to get information by ID, we're using an + // undocumented call that is normally used to display information bubbles + // when hovering over anime/manga titles at the website. The downside is, + // it doesn't provide all the information we need. + http_request.url.path = L"/includes/ajax.inc.php"; + http_request.url.query[L"t"] = L"64"; + http_request.url.query[L"id"] = request.data[canonical_name_ + L"-id"]; +} + +void Service::SearchTitle(Request& request, HttpRequest& http_request) { + // MAL's search method is far from perfect. Missing a punctuation mark + // (e.g. "A B" instead of "A: B") or searching for a title that is too short + // (e.g. "C", "K") can return irrelevant or no results. + http_request.url.path = L"/api/anime/search.xml"; + // TODO: We might have to do some encoding on the title here + http_request.url.query[L"q"] = request.data[L"title"]; +} + +void Service::AddLibraryEntry(Request& request, HttpRequest& http_request) { + request.data[L"action"] = L"add"; + UpdateLibraryEntry(request, http_request); +} + +void Service::DeleteLibraryEntry(Request& request, HttpRequest& http_request) { + request.data[L"action"] = L"delete"; + UpdateLibraryEntry(request, http_request); +} + +void Service::UpdateLibraryEntry(Request& request, HttpRequest& http_request) { + http_request.method = L"POST"; + http_request.header[L"Content-Type"] = L"application/x-www-form-urlencoded"; + + if (!request.data.count(L"action")) + request.data[L"action"] = L"update"; + + http_request.url.path = L"/api/animelist/" + request.data[L"action"] + L"/" + + request.data[canonical_name_ + L"-id"] + L".xml"; + + // Delete method doesn't require us to provide additional data + if (request.data[L"action"] == L"delete") + return; + + xml_document document; + xml_node node_declaration = document.append_child(pugi::node_declaration); + node_declaration.append_attribute(L"version") = L"1.0"; + node_declaration.append_attribute(L"encoding") = L"UTF-8"; + xml_node node_entry = document.append_child(L"entry"); + + // MAL allows setting a new value to "times_rewatched" and others, but + // there's no way to get the current value. So we avoid them altogether. + const wchar_t* tags[] = { + L"episode", + L"status", + L"score", +// L"downloaded_episodes", +// L"storage_type", +// L"storage_value", +// L"times_rewatched", +// L"rewatch_value", + L"date_start", + L"date_finish", +// L"priority", +// L"enable_discussion", + L"enable_rewatching", +// L"comments", +// L"fansub_group", + L"tags" + }; + std::set<std::wstring> valid_tags(tags, tags + sizeof(tags) / sizeof(*tags)); + foreach_(it, request.data) { + auto tag = valid_tags.find(TranslateKeyTo(it->first)); + if (tag != valid_tags.end()) { + std::wstring value = it->second; + if (*tag == L"status") { + value = ToWstr(TranslateMyStatusTo(ToInt(value))); + } else if (StartsWith(*tag, L"date")) { + value = TranslateMyDateTo(value); + } + XmlWriteStrValue(node_entry, tag->c_str(), value.c_str()); + } + } + + http_request.body = L"data=" + XmlGetNodeAsString(document); +} + +//////////////////////////////////////////////////////////////////////////////// +// Response handlers + +void Service::AuthenticateUser(Response& response, HttpResponse& http_response) { + response.data[canonical_name_ + L"-username"] = + InStr(http_response.body, L"<username>", L"</username>"); +} + +void Service::GetLibraryEntries(Response& response, HttpResponse& http_response) { + xml_document document; + xml_parse_result parse_result = document.load(http_response.body.c_str()); + + if (parse_result.status != pugi::status_ok) { + response.data[L"error"] = L"Could not parse the list"; + return; + } + + xml_node node_myanimelist = document.child(L"myanimelist"); + + // Available tags: + // - user_id + // - user_name + // - user_watching + // - user_completed + // - user_onhold + // - user_dropped + // - user_plantowatch + // - user_days_spent_watching + xml_node node_myinfo = node_myanimelist.child(L"myinfo"); + user_.id = XmlReadStrValue(node_myinfo, L"user_id"); + user_.username = XmlReadStrValue(node_myinfo, L"user_name"); + // We ignore the remaining tags, because MAL can be very slow at updating + // their values, and we can easily calculate them ourselves anyway. + + // Available tags: + // - series_animedb_id + // - series_title + // - series_synonyms (separated by "; ") + // - series_type + // - series_episodes + // - series_status + // - series_start + // - series_end + // - series_image + // - my_id (deprecated) + // - my_watched_episodes + // - my_start_date + // - my_finish_date + // - my_score + // - my_status + // - my_rewatching + // - my_rewatching_ep + // - my_last_updated + // - my_tags + foreach_xmlnode_(node, node_myanimelist, L"anime") { + ::anime::Item anime_item; + anime_item.SetSource(this->id()); + anime_item.SetId(XmlReadStrValue(node, L"series_animedb_id"), this->id()); + anime_item.SetLastModified(time(nullptr)); // current time + + anime_item.SetTitle(XmlReadStrValue(node, L"series_title")); + anime_item.SetSynonyms(XmlReadStrValue(node, L"series_synonyms")); + anime_item.SetType(TranslateSeriesTypeFrom(XmlReadIntValue(node, L"series_type"))); + anime_item.SetEpisodeCount(XmlReadIntValue(node, L"series_episodes")); + anime_item.SetAiringStatus(TranslateSeriesStatusFrom(XmlReadIntValue(node, L"series_status"))); + anime_item.SetDateStart(XmlReadStrValue(node, L"series_start")); + anime_item.SetDateEnd(XmlReadStrValue(node, L"series_end")); + anime_item.SetImageUrl(XmlReadStrValue(node, L"series_image")); + + anime_item.AddtoUserList(); + anime_item.SetMyLastWatchedEpisode(XmlReadIntValue(node, L"my_watched_episodes")); + anime_item.SetMyDateStart(XmlReadStrValue(node, L"my_start_date")); + anime_item.SetMyDateEnd(XmlReadStrValue(node, L"my_finish_date")); + anime_item.SetMyScore(XmlReadIntValue(node, L"my_score")); + anime_item.SetMyStatus(TranslateMyStatusFrom(XmlReadIntValue(node, L"my_status"))); + anime_item.SetMyRewatching(XmlReadIntValue(node, L"my_rewatching")); + anime_item.SetMyRewatchingEp(XmlReadIntValue(node, L"my_rewatching_ep")); + anime_item.SetMyLastUpdated(XmlReadStrValue(node, L"my_last_updated")); + anime_item.SetMyTags(XmlReadStrValue(node, L"my_tags")); + + AnimeDatabase.UpdateItem(anime_item); + } +} + +void Service::GetMetadataById(Response& response, HttpResponse& http_response) { + // Available data: + // - ID + // - Title (truncated, followed by year aired) + // - Synopsis (limited to 200 characters) + // - Genres + // - Status (in string form) + // - Type (in string form) + // - Episodes + // - Score + // - Rank + // - Popularity + // - Members + string_t id = InStr(http_response.body, + L"/anime/", L"/"); + string_t title = InStr(http_response.body, + L"class=\"hovertitle\">", L"</a>"); + string_t genres = InStr(http_response.body, + L"Genres:</span> ", L"<br />"); + string_t status = InStr(http_response.body, + L"Status:</span> ", L"<br />"); + string_t type = InStr(http_response.body, + L"Type:</span> ", L"<br />"); + string_t episodes = InStr(http_response.body, + L"Episodes:</span> ", L"<br />"); + string_t score = InStr(http_response.body, + L"Score:</span> ", L"<br />"); + string_t popularity = InStr(http_response.body, + L"Popularity:</span> ", L"<br />"); + + bool title_is_truncated = false; + + if (EndsWith(title, L")") && title.length() > 7) + title = title.substr(0, title.length() - 7); + if (EndsWith(title, L"...") && title.length() > 3) { + title = title.substr(0, title.length() - 3); + title_is_truncated = true; + } + + std::vector<std::wstring> genres_vector; + Split(genres, L", ", genres_vector); + + StripHtmlTags(score); + int pos = InStr(score, L" (", 0); + if (pos > -1) + score.resize(pos); + + ::anime::Item anime_item; + anime_item.SetSource(this->id()); + anime_item.SetId(id, this->id()); + if (!title_is_truncated) + anime_item.SetTitle(title); + anime_item.SetAiringStatus(TranslateSeriesStatusFrom(status)); + anime_item.SetType(TranslateSeriesTypeFrom(type)); + anime_item.SetEpisodeCount(ToInt(episodes)); + anime_item.SetGenres(genres_vector); + anime_item.SetPopularity(popularity); + anime_item.SetScore(score); + anime_item.SetLastModified(time(nullptr)); // current time + + AnimeDatabase.UpdateItem(anime_item); +} + +void Service::SearchTitle(Response& response, HttpResponse& http_response) { + xml_document document; + xml_parse_result parse_result = document.load(http_response.body.c_str()); + + if (parse_result.status != pugi::status_ok) { + response.data[L"error"] = L"Could not parse search results"; + return; + } + + xml_node node_anime = document.child(L"anime"); + + // Available tags: + // - id + // - title (must be decoded) + // - english (must be decoded) + // - synonyms (must be decoded) + // - episodes + // - score + // - type + // - status + // - start_date + // - end_date + // - synopsis (must be decoded) + // - image + foreach_xmlnode_(node, node_anime, L"entry") { + ::anime::Item anime_item; + anime_item.SetSource(this->id()); + anime_item.SetId(XmlReadStrValue(node, L"id"), this->id()); + anime_item.SetTitle(DecodeText(XmlReadStrValue(node, L"title"))); + anime_item.SetEnglishTitle(DecodeText(XmlReadStrValue(node, L"english"))); + anime_item.SetSynonyms(DecodeText(XmlReadStrValue(node, L"synonyms"))); + anime_item.SetEpisodeCount(XmlReadIntValue(node, L"episodes")); + anime_item.SetScore(XmlReadStrValue(node, L"score")); + anime_item.SetType(TranslateSeriesTypeFrom(XmlReadStrValue(node, L"type"))); + anime_item.SetAiringStatus(TranslateSeriesStatusFrom(XmlReadStrValue(node, L"status"))); + anime_item.SetDateStart(XmlReadStrValue(node, L"start_date")); + anime_item.SetDateEnd(XmlReadStrValue(node, L"end_date")); + std::wstring synopsis = DecodeText(XmlReadStrValue(node, L"synopsis")); + if (!StartsWith(synopsis, L"No synopsis has been added for this series yet")) + anime_item.SetSynopsis(synopsis); + anime_item.SetImageUrl(XmlReadStrValue(node, L"image")); + anime_item.SetLastModified(time(nullptr)); // current time + + int anime_id = AnimeDatabase.UpdateItem(anime_item); + + // We return a list of IDs so that we can display the results afterwards + AppendString(response.data[L"ids"], ToWstr(anime_id), L","); + } +} + +void Service::AddLibraryEntry(Response& response, HttpResponse& http_response) { + // Nothing to do here +} + +void Service::DeleteLibraryEntry(Response& response, HttpResponse& http_response) { + // Nothing to do here +} + +void Service::UpdateLibraryEntry(Response& response, HttpResponse& http_response) { + // Nothing to do here +} + +//////////////////////////////////////////////////////////////////////////////// + +bool Service::RequestNeedsAuthentication(RequestType request_type) const { + switch (request_type) { + case kAddLibraryEntry: + case kAuthenticateUser: + case kDeleteLibraryEntry: + case kSearchTitle: + case kUpdateLibraryEntry: + return true; + } + + return false; +} + +bool Service::RequestSucceeded(Response& response, + const HttpResponse& http_response) { + // No content + if (http_response.code == 204 || http_response.body.empty()) { + response.data[L"error"] = name() + L" returned an empty response"; + return false; + } + + // Unauthorized + if (http_response.code == 401) { + // MAL doesn't return a meaningful explanation, so we'll just assume that + // either the username or the password is wrong + response.data[L"error"] = + L"Authorization failed (invalid username or password)"; + return false; + } + + switch (response.type) { + case kAddLibraryEntry: + if (IsNumeric(http_response.body)) + return true; + if (InStr(http_response.body, L"This anime is already on your list") > -1) + return true; + // TODO: Remove when MAL fixes its API + if (InStr(http_response.body, L"<title>201 Created") > -1) + return true; + break; + case kAuthenticateUser: + if (InStr(http_response.body, L"") > -1) + return true; + break; + case kDeleteLibraryEntry: + if (IsEqual(http_response.body, L"Deleted")) + return true; + break; + case kGetLibraryEntries: + if (InStr(http_response.body, L"", 0, true) > -1 && + InStr(http_response.body, L"", 0, true) > -1) + return true; + break; + case kGetMetadataById: + if (!InStr(http_response.body, L"/anime/", L"/").empty()) + return true; + break; + case kSearchTitle: + return true; + case kUpdateLibraryEntry: + if (IsEqual(http_response.body, L"Updated")) + return true; + break; + } + + // Set the error message on failure + switch (response.type) { + case kAddLibraryEntry: + case kDeleteLibraryEntry: + case kUpdateLibraryEntry: { + std::wstring error_message = http_response.body; + Replace(error_message, L"
", L"\r\n"); + StripHtmlTags(error_message); + response.data[L"error"] = error_message; + break; + } + case kGetLibraryEntries: + response.data[L"error"] = name() + L" returned an invalid response"; + break; + default: + response.data[L"error"] = L"Request failed for an unknown reason"; + break; + } + + return false; +} + +} // namespace myanimelist +} // namespace sync \ No newline at end of file diff --git a/src/sync/myanimelist.h b/src/sync/myanimelist.h new file mode 100644 index 000000000..9018cceef --- /dev/null +++ b/src/sync/myanimelist.h @@ -0,0 +1,55 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_SYNC_MYANIMELIST_H +#define TAIGA_SYNC_MYANIMELIST_H + +#include "base/types.h" +#include "sync/service.h" + +namespace sync { +namespace myanimelist { + +// API documentation: +// http://myanimelist.net/modules.php?go=api + +class Service : public sync::Service { +public: + Service(); + ~Service() {} + + void BuildRequest(Request& request, HttpRequest& http_request); + void HandleResponse(Response& response, HttpResponse& http_response); + bool RequestNeedsAuthentication(RequestType request_type) const; + +private: + REQUEST_AND_RESPONSE(AddLibraryEntry); + REQUEST_AND_RESPONSE(AuthenticateUser); + REQUEST_AND_RESPONSE(DeleteLibraryEntry); + REQUEST_AND_RESPONSE(GetLibraryEntries); + REQUEST_AND_RESPONSE(GetMetadataById); + REQUEST_AND_RESPONSE(SearchTitle); + REQUEST_AND_RESPONSE(UpdateLibraryEntry); + + bool RequestSucceeded(Response& response, const HttpResponse& http_response); +}; + +} // namespace myanimelist +} // namespace sync + +#endif // TAIGA_SYNC_MYANIMELIST_H \ No newline at end of file diff --git a/src/sync/myanimelist_types.h b/src/sync/myanimelist_types.h new file mode 100644 index 000000000..c5fec94cb --- /dev/null +++ b/src/sync/myanimelist_types.h @@ -0,0 +1,51 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_SYNC_MYANIMELIST_TYPES_H +#define TAIGA_SYNC_MYANIMELIST_TYPES_H + +namespace sync { +namespace myanimelist { + +enum SeriesStatus { + kAiring = 1, + kFinishedAiring, + kNotYetAired +}; + +enum SeriesType { + kTv = 1, + kOva, + kMovie, + kSpecial, + kOna, + kMusic +}; + +enum MyStatus { + kWatching = 1, + kCompleted, + kOnHold, + kDropped, + kPlanToWatch = 6 +}; + +} // namespace myanimelist +} // namespace sync + +#endif // TAIGA_SYNC_MYANIMELIST_TYPES_H \ No newline at end of file diff --git a/src/sync/myanimelist_util.cpp b/src/sync/myanimelist_util.cpp new file mode 100644 index 000000000..728b61637 --- /dev/null +++ b/src/sync/myanimelist_util.cpp @@ -0,0 +1,200 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/file.h" +#include "base/html.h" +#include "base/log.h" +#include "base/string.h" +#include "base/time.h" +#include "library/anime.h" +#include "library/anime_db.h" +#include "sync/myanimelist_util.h" +#include "sync/myanimelist_types.h" +#include "sync/service.h" +#include "taiga/settings.h" + +namespace sync { +namespace myanimelist { + +std::wstring DecodeText(std::wstring text) { + Replace(text, L"
", L"\r", true); + Replace(text, L"\n\n", L"\r\n\r\n", true); + + StripHtmlTags(text); + DecodeHtmlEntities(text); + + return text; +} + +//////////////////////////////////////////////////////////////////////////////// + +int TranslateSeriesStatusFrom(int value) { + switch (value) { + case kAiring: return anime::kAiring; + case kFinishedAiring: return anime::kFinishedAiring; + case kNotYetAired: return anime::kNotYetAired; + } + + LOG(LevelWarning, L"Unknown value: " + ToWstr(value)); + return anime::kUnknownStatus; +} + +int TranslateSeriesStatusFrom(const std::wstring& value) { + if (IsEqual(value, L"Currently airing")) { + return anime::kAiring; + } else if (IsEqual(value, L"Finished airing")) { + return anime::kFinishedAiring; + } else if (IsEqual(value, L"Not yet aired")) { + return anime::kNotYetAired; + } + + LOG(LevelWarning, L"Unknown value: " + value); + return anime::kUnknownStatus; +} + +int TranslateSeriesTypeFrom(int value) { + switch (value) { + case kTv: return anime::kTv; + case kOva: return anime::kOva; + case kMovie: return anime::kMovie; + case kSpecial: return anime::kSpecial; + case kOna: return anime::kOna; + case kMusic: return anime::kMusic; + } + + LOG(LevelWarning, L"Unknown value: " + ToWstr(value)); + return anime::kUnknownType; +} + +int TranslateSeriesTypeFrom(const std::wstring& value) { + if (IsEqual(value, L"TV")) { + return anime::kTv; + } else if (IsEqual(value, L"OVA")) { + return anime::kOva; + } else if (IsEqual(value, L"Movie")) { + return anime::kMovie; + } else if (IsEqual(value, L"Special")) { + return anime::kSpecial; + } else if (IsEqual(value, L"ONA")) { + return anime::kOna; + } else if (IsEqual(value, L"Music")) { + return anime::kMusic; + } + + LOG(LevelWarning, L"Unknown value: " + value); + return anime::kUnknownType; +} + +std::wstring TranslateMyDateTo(const std::wstring& value) { + Date date(value); + + // Convert YYYY-MM-DD to MMDDYYYY + return PadChar(ToWstr(date.month), '0', 2) + + PadChar(ToWstr(date.day), '0', 2) + + PadChar(ToWstr(date.year), '0', 4); +} + +int TranslateMyStatusFrom(int value) { + switch (value) { + case kWatching: return anime::kWatching; + case kCompleted: return anime::kCompleted; + case kOnHold: return anime::kOnHold; + case kDropped: return anime::kDropped; + case kPlanToWatch: return anime::kPlanToWatch; + } + + LOG(LevelWarning, L"Unknown value: " + ToWstr(value)); + return anime::kNotInList; +} + +int TranslateMyStatusTo(int value) { + switch (value) { + case anime::kWatching: return kWatching; + case anime::kCompleted: return kCompleted; + case anime::kOnHold: return kOnHold; + case anime::kDropped: return kDropped; + case anime::kPlanToWatch: return kPlanToWatch; + } + + LOG(LevelWarning, L"Unknown value: " + ToWstr(value)); + return kWatching; +} + +std::wstring TranslateKeyTo(const std::wstring& key) { + if (IsEqual(key, L"episode")) { + return key; + } else if (IsEqual(key, L"status")) { + return key; + } else if (IsEqual(key, L"score")) { + return key; + } else if (IsEqual(key, L"date_start")) { + return key; + } else if (IsEqual(key, L"date_finish")) { + return key; + } else if (IsEqual(key, L"enable_rewatching")) { + return key; + } else if (IsEqual(key, L"tags")) { + return key; + } + + return std::wstring(); +} + +//////////////////////////////////////////////////////////////////////////////// + +std::wstring GetAnimePage(const anime::Item& anime_item) { + return L"http://myanimelist.net/anime/" + + anime_item.GetId(sync::kMyAnimeList) + L"/"; +} + +void ViewAnimePage(int anime_id) { + auto anime_item = AnimeDatabase.FindItem(anime_id); + + ExecuteLink(GetAnimePage(*anime_item)); +} + +void ViewAnimeSearch(const std::wstring& title) { + ExecuteLink(L"http://myanimelist.net/anime.php?q=" + title); +} + +void ViewHistory() { + ExecuteLink(L"http://myanimelist.net/history/" + + Settings[taiga::kSync_Service_Mal_Username]); +} + +void ViewPanel() { + ExecuteLink(L"http://myanimelist.net/panel.php"); +} + +void ViewProfile() { + ExecuteLink(L"http://myanimelist.net/profile/" + + Settings[taiga::kSync_Service_Mal_Username]); +} + +void ViewUpcomingAnime() { + Date date = GetDate(); + + ExecuteLink(L"http://myanimelist.net/anime.php" + L"?sd=" + ToWstr(date.day) + + L"&sm=" + ToWstr(date.month) + + L"&sy=" + ToWstr(date.year) + + L"&em=0&ed=0&ey=0&o=2&w=&c[]=a&c[]=d&cv=1"); +} + +} // namespace myanimelist +} // namespace sync \ No newline at end of file diff --git a/src/sync/myanimelist_util.h b/src/sync/myanimelist_util.h new file mode 100644 index 000000000..9e781d7aa --- /dev/null +++ b/src/sync/myanimelist_util.h @@ -0,0 +1,53 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_SYNC_MYANIMELIST_UTIL_H +#define TAIGA_SYNC_MYANIMELIST_UTIL_H + +#include + +namespace anime { +class Item; +} + +namespace sync { +namespace myanimelist { + +std::wstring DecodeText(std::wstring text); + +int TranslateSeriesStatusFrom(int value); +int TranslateSeriesStatusFrom(const std::wstring& value); +int TranslateSeriesTypeFrom(int value); +int TranslateSeriesTypeFrom(const std::wstring& value); +std::wstring TranslateMyDateTo(const std::wstring& value); +int TranslateMyStatusFrom(int value); +int TranslateMyStatusTo(int value); +std::wstring TranslateKeyTo(const std::wstring& key); + +std::wstring GetAnimePage(const anime::Item& anime_item); +void ViewAnimePage(int anime_id); +void ViewAnimeSearch(const std::wstring& title); +void ViewHistory(); +void ViewPanel(); +void ViewProfile(); +void ViewUpcomingAnime(); + +} // namespace myanimelist +} // namespace sync + +#endif // TAIGA_SYNC_MYANIMELIST_UTIL_H \ No newline at end of file diff --git a/src/sync/service.cpp b/src/sync/service.cpp new file mode 100644 index 000000000..7df806f4e --- /dev/null +++ b/src/sync/service.cpp @@ -0,0 +1,61 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "service.h" + +namespace sync { + +Request::Request() + : service_id(kAllServices), type(kGenericRequest) { +} + +Request::Request(RequestType type) + : service_id(kAllServices), type(type) { +} + +Response::Response() + : service_id(kAllServices), type(kGenericRequest) { +} + +//////////////////////////////////////////////////////////////////////////////// + +Service::Service() + : id_(0) { +} + +bool Service::RequestNeedsAuthentication(RequestType request_type) const { + return false; +} + +const string_t& Service::host() const { + return host_; +} + +enum_t Service::id() const { + return id_; +} + +const string_t& Service::canonical_name() const { + return canonical_name_; +} + +const string_t& Service::name() const { + return name_; +} + +} // namespace sync \ No newline at end of file diff --git a/src/sync/service.h b/src/sync/service.h new file mode 100644 index 000000000..3cd307e53 --- /dev/null +++ b/src/sync/service.h @@ -0,0 +1,117 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_SYNC_SERVICE_H +#define TAIGA_SYNC_SERVICE_H + +#include "base/types.h" + +// A service, in Taiga's terms, is a web application that provides an API that +// is based on HTTP requests and responses. Services are used for metadata +// retrieval and library synchronization. At least one service must be enabled +// for Taiga to function properly. + +namespace sync { + +enum ServiceId { + kAllServices = 0, + kTaiga = 0, + kFirstService = 1, + kMyAnimeList = 1, + kHummingbird = 2, + kLastService = 2 +}; + +enum RequestType { + kGenericRequest, + kAuthenticateUser, + kGetMetadataById, + kGetMetadataByIdV2, + kSearchTitle, + kAddLibraryEntry, + kDeleteLibraryEntry, + kGetLibraryEntries, + kUpdateLibraryEntry +}; + +class Request { +public: + Request(); + Request(RequestType type); + virtual ~Request() {} + + ServiceId service_id; + RequestType type; + dictionary_t data; +}; + +class Response { +public: + Response(); + virtual ~Response() {} + + ServiceId service_id; + RequestType type; + dictionary_t data; +}; + +class User { +public: + string_t id; + string_t username; +}; + +class Service { +public: + Service(); + virtual ~Service() {} + + virtual void BuildRequest(Request& request, HttpRequest& http_request) = 0; + virtual void HandleResponse(Response& response, HttpResponse& http_response) = 0; + virtual bool RequestNeedsAuthentication(RequestType request_type) const; + + const string_t& host() const; + enum_t id() const; + const string_t& canonical_name() const; + const string_t& name() const; + +protected: + // API end-point + string_t host_; + // Service identifiers + enum_t id_; + string_t canonical_name_; + string_t name_; + // User information + User user_; +}; + +// Creates two overloaded functions: First one is to build a request, second +// one is to handle a response. +#define REQUEST_AND_RESPONSE(f) \ + void f(Request& request, HttpRequest& http_request); \ + void f(Response& response, HttpResponse& http_response); +// Other helper macros +#define BUILD_HTTP_REQUEST(type, function) \ + case type: function(request, http_request); break; +#define HANDLE_HTTP_RESPONSE(type, function) \ + case type: function(response, http_response); break; + +} // namespace sync + +#endif // TAIGA_SYNC_SERVICE_H \ No newline at end of file diff --git a/src/sync/sync.cpp b/src/sync/sync.cpp new file mode 100644 index 000000000..343b460f6 --- /dev/null +++ b/src/sync/sync.cpp @@ -0,0 +1,245 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/crypto.h" +#include "base/foreach.h" +#include "base/string.h" +#include "base/url.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "library/history.h" +#include "sync/manager.h" +#include "sync/sync.h" +#include "taiga/http.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" +#include "ui/dialog.h" +#include "ui/ui.h" + +namespace sync { + +void AuthenticateUser() { + Request request(kAuthenticateUser); + SetActiveServiceForRequest(request); + if (!AddAuthenticationToRequest(request)) + return; + ServiceManager.MakeRequest(request); +} + +void GetLibraryEntries() { + Request request(kGetLibraryEntries); + SetActiveServiceForRequest(request); + if (!AddAuthenticationToRequest(request)) + return; + ServiceManager.MakeRequest(request); +} + +void GetMetadataById(int id) { + Request request(kGetMetadataById); + SetActiveServiceForRequest(request); + if (!AddAuthenticationToRequest(request)) + return; + AddServiceDataToRequest(request, id); + ServiceManager.MakeRequest(request); +} + +// This is just a temporary method we use until Hummingbird improves their API, +// and it is only called after we receive a kGetMetadataById response. +void GetMetadataByIdV2(int id) { + Request request(kGetMetadataByIdV2); + SetActiveServiceForRequest(request); + if (!AddAuthenticationToRequest(request)) + return; + AddServiceDataToRequest(request, id); + ServiceManager.MakeRequest(request); +} + +void SearchTitle(string_t title, int id) { + Request request(kSearchTitle); + SetActiveServiceForRequest(request); + if (!AddAuthenticationToRequest(request)) + return; + if (id != anime::ID_UNKNOWN) + AddServiceDataToRequest(request, id); + request.data[L"title"] = title; + ServiceManager.MakeRequest(request); +} + +void Synchronize() { + if (!Taiga.logged_in) { + if (!taiga::GetCurrentUsername().empty() && + !taiga::GetCurrentPassword().empty()) { + // Log in + ui::ChangeStatusText(L"Logging in..."); + ui::EnableDialogInput(ui::kDialogMain, false); + AuthenticateUser(); + } else if (!taiga::GetCurrentUsername().empty()) { + // Download list + ui::ChangeStatusText(L"Downloading anime list..."); + ui::EnableDialogInput(ui::kDialogMain, false); + GetLibraryEntries(); + } else { + ui::ChangeStatusText( + L"Cannot synchronize, username and password not available"); + } + } else { + if (History.queue.GetItemCount() > 0) { + // Update items in queue + History.queue.Check(false); + } else { + // Retrieve list + ui::ChangeStatusText(L"Synchronizing anime list..."); + ui::EnableDialogInput(ui::kDialogMain, false); + GetLibraryEntries(); + } + } +} + +void UpdateLibraryEntry(AnimeValues& anime_values, int id, + taiga::HttpClientMode http_client_mode) { + RequestType request_type = ClientModeToRequestType(http_client_mode); + + Request request(request_type); + SetActiveServiceForRequest(request); + if (!AddAuthenticationToRequest(request)) + return; + AddServiceDataToRequest(request, id); + + if (anime_values.episode) + request.data[L"episode"] = ToWstr(*anime_values.episode); + if (anime_values.status) + request.data[L"status"] = ToWstr(*anime_values.status); + if (anime_values.score) + request.data[L"score"] = ToWstr(*anime_values.score); + if (anime_values.date_start) + request.data[L"date_start"] = *anime_values.date_start; + if (anime_values.date_finish) + request.data[L"date_finish"] = *anime_values.date_finish; + if (anime_values.enable_rewatching) + request.data[L"enable_rewatching"] = ToWstr(*anime_values.enable_rewatching); + if (anime_values.tags) + request.data[L"tags"] = *anime_values.tags; + + ServiceManager.MakeRequest(request); +} + +void DownloadImage(int id, const string_t& image_url) { + if (image_url.empty()) + return; + + HttpRequest http_request; + http_request.url = image_url; + http_request.parameter = id; + + auto& client = ConnectionManager.GetClient(http_request); + client.set_download_path(::anime::GetImagePath(id)); + ConnectionManager.MakeRequest(client, http_request, + taiga::kHttpGetLibraryEntryImage); +} + +//////////////////////////////////////////////////////////////////////////////// + +bool AddAuthenticationToRequest(Request& request) { + if (RequestNeedsAuthentication(request.type, request.service_id)) + if (taiga::GetCurrentUsername().empty() || + taiga::GetCurrentPassword().empty()) + return false; // Authentication is required but not available + + auto service = taiga::GetCurrentService(); + request.data[service->canonical_name() + L"-username"] = + taiga::GetCurrentUsername(); + request.data[service->canonical_name() + L"-password"] = + taiga::GetCurrentPassword(); + + return true; +} + +bool AddServiceDataToRequest(Request& request, int id) { + request.data[L"taiga-id"] = ToWstr(id); + + auto anime_item = AnimeDatabase.FindItem(id); + + if (!anime_item) + return false; + + request.data[ServiceManager.service(kMyAnimeList)->canonical_name() + L"-id"] = + anime_item->GetId(kMyAnimeList); + request.data[ServiceManager.service(kHummingbird)->canonical_name() + L"-id"] = + anime_item->GetId(kHummingbird); + + return true; +} + +bool RequestNeedsAuthentication(RequestType request_type, ServiceId service_id) { + auto service = ServiceManager.service(service_id); + return service->RequestNeedsAuthentication(request_type); +} + +void SetActiveServiceForRequest(Request& request) { + request.service_id = taiga::GetCurrentServiceId(); +} + +//////////////////////////////////////////////////////////////////////////////// + +RequestType ClientModeToRequestType(taiga::HttpClientMode client_mode) { + switch (client_mode) { + case taiga::kHttpServiceAuthenticateUser: + return kAuthenticateUser; + case taiga::kHttpServiceGetMetadataById: + return kGetMetadataById; + case taiga::kHttpServiceGetMetadataByIdV2: + return kGetMetadataByIdV2; + case taiga::kHttpServiceSearchTitle: + return kSearchTitle; + case taiga::kHttpServiceAddLibraryEntry: + return kAddLibraryEntry; + case taiga::kHttpServiceDeleteLibraryEntry: + return kDeleteLibraryEntry; + case taiga::kHttpServiceGetLibraryEntries: + return kGetLibraryEntries; + case taiga::kHttpServiceUpdateLibraryEntry: + return kUpdateLibraryEntry; + default: + return kGenericRequest; + } +} + +taiga::HttpClientMode RequestTypeToClientMode(RequestType request_type) { + switch (request_type) { + case kAuthenticateUser: + return taiga::kHttpServiceAuthenticateUser; + case kGetMetadataById: + return taiga::kHttpServiceGetMetadataById; + case kGetMetadataByIdV2: + return taiga::kHttpServiceGetMetadataByIdV2; + case kSearchTitle: + return taiga::kHttpServiceSearchTitle; + case kAddLibraryEntry: + return taiga::kHttpServiceAddLibraryEntry; + case kDeleteLibraryEntry: + return taiga::kHttpServiceDeleteLibraryEntry; + case kGetLibraryEntries: + return taiga::kHttpServiceGetLibraryEntries; + case kUpdateLibraryEntry: + return taiga::kHttpServiceUpdateLibraryEntry; + default: + return taiga::kHttpSilent; + } +} + +} // namespace sync \ No newline at end of file diff --git a/src/sync/sync.h b/src/sync/sync.h new file mode 100644 index 000000000..f06035293 --- /dev/null +++ b/src/sync/sync.h @@ -0,0 +1,50 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_SYNC_SYNC_H +#define TAIGA_SYNC_SYNC_H + +#include "service.h" +#include "base/types.h" +#include "library/history.h" +#include "taiga/http.h" + +namespace sync { + +void AuthenticateUser(); +void GetLibraryEntries(); +void GetMetadataById(int id); +void GetMetadataByIdV2(int id); +void SearchTitle(string_t title, int id); +void Synchronize(); +void UpdateLibraryEntry(AnimeValues& anime_values, int id, + taiga::HttpClientMode http_client_mode); + +void DownloadImage(int id, const std::wstring& image_url); + +bool AddAuthenticationToRequest(Request& request); +bool AddServiceDataToRequest(Request& request, int id); +bool RequestNeedsAuthentication(RequestType request_type, ServiceId service_id); +void SetActiveServiceForRequest(Request& request); + +RequestType ClientModeToRequestType(taiga::HttpClientMode client_mode); +taiga::HttpClientMode RequestTypeToClientMode(RequestType request_type); + +} // namespace sync + +#endif // TAIGA_SYNC_SYNC_H \ No newline at end of file diff --git a/src/taiga/action.cpp b/src/taiga/action.cpp new file mode 100644 index 000000000..4a170b83a --- /dev/null +++ b/src/taiga/action.cpp @@ -0,0 +1,566 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/log.h" +#include "base/process.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "library/discover.h" +#include "library/history.h" +#include "sync/hummingbird_util.h" +#include "sync/myanimelist_util.h" +#include "sync/sync.h" +#include "taiga/announce.h" +#include "taiga/resource.h" +#include "taiga/settings.h" +#include "track/monitor.h" +#include "track/recognition.h" +#include "track/search.h" +#include "ui/dlg/dlg_main.h" +#include "ui/dlg/dlg_search.h" +#include "ui/dlg/dlg_season.h" +#include "ui/dlg/dlg_settings.h" +#include "ui/dlg/dlg_torrent.h" +#include "ui/dlg/dlg_feed_filter.h" +#include "ui/dialog.h" +#include "ui/menu.h" +#include "ui/ui.h" +#include "win/win_commondialog.h" + +void ExecuteAction(std::wstring action, WPARAM wParam, LPARAM lParam) { + LOG(LevelDebug, action); + + std::wstring body; + size_t pos = action.find('('); + if (pos != action.npos) { + body = action.substr(pos + 1, action.find_last_of(')') - (pos + 1)); + action.resize(pos); + } + Trim(body); + Trim(action); + if (action.empty()) + return; + + ////////////////////////////////////////////////////////////////////////////// + // Taiga + + // CheckUpdates() + // Checks for a new version of the program. + if (action == L"CheckUpdates") { + ui::ShowDialog(ui::kDialogUpdate); + + // Exit(), Quit() + // Exits from Taiga. + } else if (action == L"Exit" || action == L"Quit") { + ui::DlgMain.Destroy(); + + ////////////////////////////////////////////////////////////////////////////// + // Services + + // Synchronize() + // Synchronizes local and remote lists. + } else if (action == L"Synchronize") { + sync::Synchronize(); + + // SearchAnime() + } else if (action == L"SearchAnime") { + if (body.empty()) + return; + auto service = taiga::GetCurrentService(); + if (service->RequestNeedsAuthentication(sync::kSearchTitle)) { + if (taiga::GetCurrentUsername().empty() || + taiga::GetCurrentPassword().empty()) { + ui::OnSettingsAccountEmpty(); + return; + } + } + ui::DlgMain.navigation.SetCurrentPage(ui::kSidebarItemSearch); + ui::DlgMain.edit.SetText(body); + ui::DlgSearch.Search(body); + + // ViewAnimePage + // Opens up anime page on the active service. + // lParam is an anime ID. + } else if (action == L"ViewAnimePage") { + int anime_id = static_cast(lParam); + switch (taiga::GetCurrentServiceId()) { + case sync::kMyAnimeList: + sync::myanimelist::ViewAnimePage(anime_id); + break; + case sync::kHummingbird: + sync::hummingbird::ViewAnimePage(anime_id); + break; + } + + // ViewUpcomingAnime + // Opens up upcoming anime page on MAL. + } else if (action == L"ViewUpcomingAnime") { + switch (taiga::GetCurrentServiceId()) { + case sync::kMyAnimeList: + sync::myanimelist::ViewUpcomingAnime(); + break; + case sync::kHummingbird: + sync::hummingbird::ViewUpcomingAnime(); + break; + } + + // MalViewPanel() + // MalViewProfile() + // MalViewHistory() + // Opens up MyAnimeList user pages. + } else if (action == L"MalViewPanel") { + sync::myanimelist::ViewPanel(); + } else if (action == L"MalViewProfile") { + sync::myanimelist::ViewProfile(); + } else if (action == L"MalViewHistory") { + sync::myanimelist::ViewHistory(); + + // HummingbirdViewProfile() + // HummingbirdViewDashboard() + // HummingbirdViewRecommendations() + // Opens up Hummingbird user pages. + } else if (action == L"HummingbirdViewProfile") { + sync::hummingbird::ViewProfile(); + } else if (action == L"HummingbirdViewDashboard") { + sync::hummingbird::ViewDashboard(); + } else if (action == L"HummingbirdViewRecommendations") { + sync::hummingbird::ViewRecommendations(); + + ////////////////////////////////////////////////////////////////////////////// + + // Execute(path) + // Executes a file or folder. + } else if (action == L"Execute") { + Execute(body); + + // URL(address) + // Opens a web page. + // lParam is an anime ID. + } else if (action == L"URL") { + int anime_id = static_cast(lParam); + auto anime_item = AnimeDatabase.FindItem(anime_id); + if (anime_item) { + std::wstring title = anime_item->GetTitle(); + EraseChars(title, L"_!?.,:;~+"); + Erase(title, L" -"); + Replace(body, L"%title%", title); + } + ExecuteLink(body); + + ////////////////////////////////////////////////////////////////////////////// + // UI + + // About() + // Shows about window. + } else if (action == L"About") { + ui::ShowDialog(ui::kDialogAbout); + + // Info() + // Shows anime information window. + // lParam is an anime ID. + } else if (action == L"Info") { + int anime_id = static_cast(lParam); + ui::ShowDlgAnimeInfo(anime_id); + + // MainDialog() + } else if (action == L"MainDialog") { + ui::ShowDialog(ui::kDialogMain); + + // RecognitionTest() + // Shows recognition test window. + } else if (action == L"RecognitionTest") { + ui::ShowDialog(ui::kDialogTestRecognition); + + // Settings() + // Shows settings window. + // wParam is the initial section. + // lParam is the initial page. + } else if (action == L"Settings") { + ui::ShowDlgSettings(wParam, lParam); + + // SearchTorrents(source) + // Searches torrents from specified source URL. + // lParam is an anime ID. + } else if (action == L"SearchTorrents") { + int anime_id = static_cast(lParam); + ui::DlgTorrent.Search(body, anime_id); + + // ShowSidebar() + } else if (action == L"ShowSidebar") { + bool hide_sidebar = !Settings.GetBool(taiga::kApp_Option_HideSidebar); + Settings.Set(taiga::kApp_Option_HideSidebar, hide_sidebar); + ui::DlgMain.treeview.Show(!hide_sidebar); + ui::DlgMain.UpdateControlPositions(); + ui::Menus.UpdateView(); + + // TorrentAddFilter() + // Shows add new filter window. + // wParam is a BOOL value that represents modal status. + // lParam is the handle of the parent window. + } else if (action == L"TorrentAddFilter") { + if (!ui::DlgFeedFilter.IsWindow()) { + ui::DlgFeedFilter.Create(IDD_FEED_FILTER, + reinterpret_cast(lParam), wParam != FALSE); + } else { + ActivateWindow(ui::DlgFeedFilter.GetWindowHandle()); + } + + // ViewContent(page) + // Selects a page from sidebar. + } else if (action == L"ViewContent") { + int page = ToInt(body); + ui::DlgMain.navigation.SetCurrentPage(page); + + ////////////////////////////////////////////////////////////////////////////// + // Library + + // AddToListAs(status) + // Adds new anime to list with given status. + // lParam is an anime ID. + } else if (action == L"AddToListAs") { + int status = ToInt(body); + int anime_id = static_cast(lParam); + AnimeDatabase.AddToList(anime_id, status); + + ////////////////////////////////////////////////////////////////////////////// + // Tracker + + // AddFolder() + // Opens up a dialog to add new root folder. + } else if (action == L"AddFolder") { + std::wstring path; + if (win::BrowseForFolder(ui::GetWindowHandle(ui::kDialogMain), + L"Please select a folder:", L"", path)) { + Settings.root_folders.push_back(path); + if (Settings.GetBool(taiga::kLibrary_WatchFolders)) + FolderMonitor.Enable(); + ui::ShowDlgSettings(ui::kSettingsSectionLibrary, ui::kSettingsPageLibraryFolders); + } + + // ScanEpisodes(), ScanEpisodesAll() + // Checks episode availability. + } else if (action == L"ScanEpisodes") { + int anime_id = static_cast(lParam); + ScanAvailableEpisodes(false, anime_id, 0); + } else if (action == L"ScanEpisodesAll") { + ScanAvailableEpisodes(false); + + ////////////////////////////////////////////////////////////////////////////// + // Settings + + // ToggleRecognition() + // Enables or disables anime recognition. + } else if (action == L"ToggleRecognition") { + bool enable_recognition = !Settings.GetBool(taiga::kApp_Option_EnableRecognition); + Settings.Set(taiga::kApp_Option_EnableRecognition, enable_recognition); + if (enable_recognition) { + ui::ChangeStatusText(L"Automatic anime recognition is now enabled."); + CurrentEpisode.Set(anime::ID_UNKNOWN); + } else { + ui::ChangeStatusText(L"Automatic anime recognition is now disabled."); + auto anime_item = AnimeDatabase.FindItem(CurrentEpisode.anime_id); + CurrentEpisode.Set(anime::ID_NOTINLIST); + if (anime_item) + EndWatching(*anime_item, CurrentEpisode); + } + + // ToggleSharing() + // Enables or disables automatic sharing. + } else if (action == L"ToggleSharing") { + bool enable_sharing = !Settings.GetBool(taiga::kApp_Option_EnableSharing); + Settings.Set(taiga::kApp_Option_EnableSharing, enable_sharing); + ui::Menus.UpdateTools(); + if (enable_sharing) { + ui::ChangeStatusText(L"Automatic sharing is now enabled."); + } else { + ui::ChangeStatusText(L"Automatic sharing is now disabled."); + } + + // ToggleSynchronization() + // Enables or disables automatic list synchronization. + } else if (action == L"ToggleSynchronization") { + bool enable_sync = !Settings.GetBool(taiga::kApp_Option_EnableSync); + Settings.Set(taiga::kApp_Option_EnableSync, enable_sync); + ui::Menus.UpdateTools(); + if (enable_sync) { + ui::ChangeStatusText(L"Automatic synchronization is now enabled."); + } else { + ui::ChangeStatusText(L"Automatic synchronization is now disabled."); + } + + ////////////////////////////////////////////////////////////////////////////// + // Sharing + + // AnnounceToHTTP(force) + // Sends an HTTP request. + } else if (action == L"AnnounceToHTTP") { + Announcer.Do(taiga::kAnnounceToHttp, nullptr, body == L"true"); + + // AnnounceToMIRC(force) + // Sends message to specified channels in mIRC. + } else if (action == L"AnnounceToMIRC") { + Announcer.Do(taiga::kAnnounceToMirc, nullptr, body == L"true"); + + // AnnounceToSkype(force) + // Changes Skype mood text. + // Requires authorization. + } else if (action == L"AnnounceToSkype") { + Announcer.Do(taiga::kAnnounceToSkype, nullptr, body == L"true"); + + // AnnounceToTwitter(force) + // Changes Twitter status. + } else if (action == L"AnnounceToTwitter") { + Announcer.Do(taiga::kAnnounceToTwitter, nullptr, body == L"true"); + + ////////////////////////////////////////////////////////////////////////////// + + // EditAll([anime_id]) + // Shows a dialog to edit details of an anime. + // lParam is an anime ID. + } else if (action == L"EditAll") { + int anime_id = body.empty() ? static_cast(lParam) : ToInt(body); + ui::ShowDlgAnimeEdit(anime_id); + + // EditDelete() + // Removes an anime from list. + // lParam is an anime ID. + } else if (action == L"EditDelete") { + int anime_id = static_cast(lParam); + if (ui::OnLibraryEntryEditDelete(anime_id)) { + HistoryItem history_item; + history_item.anime_id = anime_id; + history_item.mode = taiga::kHttpServiceDeleteLibraryEntry; + History.queue.Add(history_item); + } + + // EditEpisode(value) + // Changes watched episode value of an anime. + // Value is optional. + // lParam is an anime ID. + } else if (action == L"EditEpisode") { + int anime_id = static_cast(lParam); + int value = body.empty() ? + ui::OnLibraryEntryEditEpisode(anime_id) : ToInt(body); + anime::ChangeEpisode(anime_id, value); + // DecrementEpisode() + // lParam is an anime ID. + } else if (action == L"DecrementEpisode") { + int anime_id = static_cast(lParam); + anime::DecrementEpisode(anime_id); + // IncrementEpisode() + // lParam is an anime ID. + } else if (action == L"IncrementEpisode") { + int anime_id = static_cast(lParam); + anime::IncrementEpisode(anime_id); + + // EditScore(value) + // Changes anime score. + // Value must be between 0-10 and different from current score. + // lParam is an anime ID. + } else if (action == L"EditScore") { + int anime_id = static_cast(lParam); + HistoryItem history_item; + history_item.anime_id = anime_id; + history_item.score = ToInt(body); + history_item.mode = taiga::kHttpServiceUpdateLibraryEntry; + History.queue.Add(history_item); + + // EditStatus(value) + // Changes anime status of user. + // Value must be 1, 2, 3, 4 or 6, and different from current status. + // lParam is an anime ID. + } else if (action == L"EditStatus") { + HistoryItem history_item; + history_item.status = ToInt(body); + int anime_id = static_cast(lParam); + auto anime_item = AnimeDatabase.FindItem(anime_id); + switch (*history_item.status) { + case anime::kCompleted: + history_item.episode = anime_item->GetEpisodeCount(); + if (*history_item.episode == 0) + history_item.episode.Reset(); + if (!anime::IsValidDate(anime_item->GetMyDateStart()) && + anime_item->GetEpisodeCount() == 1) + history_item.date_start = GetDate(); + if (!anime::IsValidDate(anime_item->GetMyDateEnd())) + history_item.date_finish = GetDate(); + break; + } + history_item.anime_id = anime_id; + history_item.mode = taiga::kHttpServiceUpdateLibraryEntry; + History.queue.Add(history_item); + + // EditTags(tags) + // Changes anime tags. + // Tags must be separated by a comma. + // lParam is an anime ID. + } else if (action == L"EditTags") { + int anime_id = static_cast(lParam); + std::wstring tags; + if (ui::OnLibraryEntryEditTags(anime_id, tags)) { + HistoryItem history_item; + history_item.anime_id = anime_id; + history_item.tags = tags; + history_item.mode = taiga::kHttpServiceUpdateLibraryEntry; + History.queue.Add(history_item); + } + + // EditTitles(titles) + // Changes alternative titles of an anime. + // Titles must be separated by "; ". + // lParam is an anime ID. + } else if (action == L"EditTitles") { + int anime_id = static_cast(lParam); + auto anime_item = AnimeDatabase.FindItem(anime_id); + std::wstring titles; + if (ui::OnLibraryEntryEditTitles(anime_id, titles)) { + anime_item->SetUserSynonyms(titles); + Meow.UpdateCleanTitles(anime_id); + Settings.Save(); + } + + ////////////////////////////////////////////////////////////////////////////// + + // OpenFolder() + // Searches for anime folder and opens it. + // lParam is an anime ID. + } else if (action == L"OpenFolder") { + int anime_id = static_cast(lParam); + auto anime_item = AnimeDatabase.FindItem(anime_id); + if (!anime_item || !anime_item->IsInList()) + return; + if (anime_item->GetFolder().empty()) { + ScanAvailableEpisodes(false, anime_item->GetId(), 0); + } + if (anime_item->GetFolder().empty()) { + if (ui::OnAnimeFolderNotFound()) { + std::wstring default_path, path; + if (!Settings.root_folders.empty()) + default_path = Settings.root_folders.front(); + if (win::BrowseForFolder(ui::GetWindowHandle(ui::kDialogMain), + L"Choose an anime folder", + default_path, path)) { + anime_item->SetFolder(path); + Settings.Save(); + } + } + } + ui::ClearStatusText(); + if (!anime_item->GetFolder().empty()) { + Execute(anime_item->GetFolder()); + } + + // SetFolder() + // Lets user set an anime folder. + // lParam is an anime ID. + } else if (action == L"SetFolder") { + int anime_id = static_cast(lParam); + auto anime_item = AnimeDatabase.FindItem(anime_id); + std::wstring path, title = L"Anime title: " + anime_item->GetTitle(); + if (win::BrowseForFolder(ui::GetWindowHandle(ui::kDialogMain), + title.c_str(), L"", path)) { + anime_item->SetFolder(path); + Settings.Save(); + ScanAvailableEpisodesQuick(anime_item->GetId()); + } + + ////////////////////////////////////////////////////////////////////////////// + + // PlayEpisode(value) + // Searches for an episode of an anime and plays it. + // lParam is an anime ID. + } else if (action == L"PlayEpisode") { + int number = ToInt(body); + int anime_id = static_cast(lParam); + anime::PlayEpisode(anime_id, number); + + // PlayLast() + // Searches for the last watched episode of an anime and plays it. + // lParam is an anime ID. + } else if (action == L"PlayLast") { + int anime_id = static_cast(lParam); + anime::PlayLastEpisode(anime_id); + + // PlayNext([anime_id]) + // Searches for the next episode of an anime and plays it. + // lParam is an anime ID. + } else if (action == L"PlayNext") { + int anime_id = body.empty() ? static_cast(lParam) : ToInt(body); + anime::PlayNextEpisode(anime_id); + + // PlayRandom() + // Searches for a random episode of an anime and plays it. + // lParam is an anime ID. + } else if (action == L"PlayRandom") { + int anime_id = body.empty() ? static_cast(lParam) : ToInt(body); + auto anime_item = AnimeDatabase.FindItem(anime_id); + if (anime_item) + anime::PlayRandomEpisode(*anime_item); + + // PlayRandomAnime() + // Searches for a random episode of a random anime and plays it. + } else if (action == L"PlayRandomAnime") { + anime::PlayRandomAnime(); + + ////////////////////////////////////////////////////////////////////////////// + + // Season_Load(file) + // Loads season data. + } else if (action == L"Season_Load") { + if (SeasonDatabase.Load(body)) { + SeasonDatabase.Review(); + ui::DlgSeason.RefreshList(); + ui::DlgSeason.RefreshStatus(); + ui::DlgSeason.RefreshToolbar(); + if (SeasonDatabase.IsRefreshRequired()) + if (ui::OnSeasonRefreshRequired()) + ui::DlgSeason.RefreshData(); + } + + // Season_GroupBy(group) + // Groups season data. + } else if (action == L"Season_GroupBy") { + ui::DlgSeason.group_by = ToInt(body); + ui::DlgSeason.RefreshList(); + ui::DlgSeason.RefreshToolbar(); + + // Season_SortBy(sort) + // Sorts season data. + } else if (action == L"Season_SortBy") { + ui::DlgSeason.sort_by = ToInt(body); + ui::DlgSeason.RefreshList(); + ui::DlgSeason.RefreshToolbar(); + + // Season_RefreshItemData() + // Refreshes an individual season item data. + } else if (action == L"Season_RefreshItemData") { + ui::DlgSeason.RefreshData(static_cast(lParam)); + + // Season_ViewAs(mode) + // Changes view mode. + } else if (action == L"Season_ViewAs") { + ui::DlgSeason.SetViewMode(ToInt(body)); + ui::DlgSeason.RefreshList(); + ui::DlgSeason.RefreshToolbar(); + + // Unknown + } else { + LOG(LevelWarning, L"Unknown action: " + action); + } +} \ No newline at end of file diff --git a/src/taiga/announce.cpp b/src/taiga/announce.cpp new file mode 100644 index 000000000..ca61e7fc4 --- /dev/null +++ b/src/taiga/announce.cpp @@ -0,0 +1,478 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/file.h" +#include "base/foreach.h" +#include "base/log.h" +#include "base/string.h" +#include "base/url.h" +#include "library/anime.h" +#include "library/anime_episode.h" +#include "taiga/announce.h" +#include "taiga/http.h" +#include "taiga/script.h" +#include "taiga/settings.h" +#include "ui/ui.h" +#include "win/win_dde.h" + +taiga::Announcer Announcer; +taiga::Skype Skype; +taiga::Twitter Twitter; + +namespace taiga { + +void Announcer::Clear(int modes, bool force) { + if (modes & kAnnounceToHttp) + if (Settings.GetBool(kShare_Http_Enabled) || force) + ToHttp(Settings[kShare_Http_Url], L""); + + if (modes & kAnnounceToSkype) + if (Settings.GetBool(kShare_Skype_Enabled) || force) + ToSkype(::Skype.previous_mood); +} + +void Announcer::Do(int modes, anime::Episode* episode, bool force) { + if (!force && !Settings.GetBool(kApp_Option_EnableSharing)) + return; + + if (!episode) + episode = &CurrentEpisode; + + if (modes & kAnnounceToHttp) { + if (Settings.GetBool(kShare_Http_Enabled) || force) { + LOG(LevelDebug, L"HTTP"); + ToHttp(Settings[kShare_Http_Url], + ReplaceVariables(Settings[kShare_Http_Format], + *episode, true, force)); + } + } + + if (episode->anime_id <= anime::ID_UNKNOWN) + return; + + if (modes & kAnnounceToMirc) { + if (Settings.GetBool(kShare_Mirc_Enabled) || force) { + LOG(LevelDebug, L"mIRC"); + ToMirc(Settings[kShare_Mirc_Service], + Settings[kShare_Mirc_Channels], + ReplaceVariables(Settings[kShare_Mirc_Format], + *episode, false, force), + Settings.GetInt(kShare_Mirc_Mode), + Settings.GetBool(kShare_Mirc_UseMeAction), + Settings.GetBool(kShare_Mirc_MultiServer)); + } + } + + if (modes & kAnnounceToSkype) { + if (Settings.GetBool(kShare_Skype_Enabled) || force) { + LOG(LevelDebug, L"Skype"); + ToSkype(ReplaceVariables(Settings[kShare_Skype_Format], + *episode, false, force)); + } + } + + if (modes & kAnnounceToTwitter) { + if (Settings.GetBool(kShare_Twitter_Enabled) || force) { + LOG(LevelDebug, L"Twitter"); + ToTwitter(ReplaceVariables(Settings[kShare_Twitter_Format], + *episode, false, force)); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// HTTP + +void Announcer::ToHttp(const std::wstring& address, const std::wstring& data) { + if (address.empty() || data.empty()) + return; + + HttpRequest http_request; + http_request.method = L"POST"; + http_request.url = address; + http_request.body = data; + + ConnectionManager.MakeRequest(http_request, taiga::kHttpSilent); +} + +//////////////////////////////////////////////////////////////////////////////// +// mIRC + +bool Announcer::ToMirc(const std::wstring& service, + std::wstring channels, + const std::wstring& data, + int mode, + BOOL use_action, + BOOL multi_server) { + if (!FindWindow(L"mIRC", nullptr)) + return false; + if (service.empty() || channels.empty() || data.empty()) + return false; + + // Initialize + win::DynamicDataExchange dde; + if (!dde.Initialize(/*APPCLASS_STANDARD | APPCMD_CLIENTONLY, TRUE*/)) { + ui::OnMircDdeInitFail(); + return false; + } + + // List channels + if (mode != kMircChannelModeCustom) { + if (dde.Connect(service, L"CHANNELS")) { + dde.ClientTransaction(L" ", L"", &channels, XTYP_REQUEST); + dde.Disconnect(); + } + } + std::vector channel_list; + Tokenize(channels, L" ,;", channel_list); + foreach_(it, channel_list) { + Trim(*it); + if (it->empty()) + continue; + if (it->at(0) == '*') { + *it = it->substr(1); + if (mode == kMircChannelModeActive) { + std::wstring temp = *it; + channel_list.clear(); + channel_list.push_back(temp); + break; + } + } + if (it->at(0) != '#') + it->insert(it->begin(), '#'); + } + + // Connect + if (!dde.Connect(service, L"COMMAND")) { + dde.UnInitialize(); + ui::OnMircDdeConnectionFail(); + return false; + } + + // Send message to channels + foreach_(it, channel_list) { + std::wstring message; + message += multi_server ? L"/scon -a " : L""; + message += use_action ? L"/describe " : L"/msg "; + message += *it + L" " + data; + dde.ClientTransaction(L" ", message, NULL, XTYP_POKE); + } + + // Clean up + dde.Disconnect(); + dde.UnInitialize(); + + return true; +} + +bool Announcer::TestMircConnection(const std::wstring& service) { + // Search for mIRC window + if (!FindWindow(L"mIRC", nullptr)) { + ui::OnMircNotRunning(true); + return false; + } + + // Initialize + win::DynamicDataExchange dde; + if (!dde.Initialize(/*APPCLASS_STANDARD | APPCMD_CLIENTONLY, TRUE*/)) { + ui::OnMircDdeInitFail(true); + return false; + } + + // Try to connect + if (!dde.Connect(service, L"CHANNELS")) { + dde.UnInitialize(); + ui::OnMircDdeConnectionFail(true); + return false; + } + + std::wstring channels; + dde.ClientTransaction(L" ", L"", &channels, XTYP_REQUEST); + + // Success + dde.Disconnect(); + dde.UnInitialize(); + ui::OnMircDdeConnectionSuccess(channels, true); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// Skype + +const UINT Skype::wm_attach = ::RegisterWindowMessage(L"SkypeControlAPIAttach"); +const UINT Skype::wm_discover = ::RegisterWindowMessage(L"SkypeControlAPIDiscover"); + +Skype::Skype() + : hwnd(nullptr), + hwnd_skype(nullptr) { +} + +Skype::~Skype() { + window_.Destroy(); +} + +void Skype::Create() { + hwnd = window_.Create(); +} + +BOOL Skype::Discover() { + PDWORD_PTR sendMessageResult = nullptr; + return SendMessageTimeout(HWND_BROADCAST, wm_discover, + reinterpret_cast(hwnd), + 0, SMTO_NORMAL, 1000, sendMessageResult); +} + +bool Skype::SendCommand(const std::wstring& command) { + std::string str = WstrToStr(command); + const char* buffer = str.c_str(); + + COPYDATASTRUCT cds; + cds.dwData = 0; + cds.lpData = (void*)buffer; + cds.cbData = strlen(buffer) + 1; + + if (SendMessage(hwnd_skype, WM_COPYDATA, + reinterpret_cast(hwnd), + reinterpret_cast(&cds)) == FALSE) { + LOG(LevelError, L"WM_COPYDATA failed."); + hwnd_skype = nullptr; + return false; + } else { + LOG(LevelDebug, L"WM_COPYDATA succeeded."); + return true; + } +} + +bool Skype::GetMoodText() { + std::wstring command = L"GET PROFILE RICH_MOOD_TEXT"; + return SendCommand(command); +} + +bool Skype::SetMoodText(const std::wstring& mood) { + current_mood = mood; + std::wstring command = L"SET PROFILE RICH_MOOD_TEXT " + mood; + return SendCommand(command); +} + +void Skype::Window::PreRegisterClass(WNDCLASSEX& wc) { + wc.lpszClassName = L"TaigaSkypeW"; +} + +void Skype::Window::PreCreate(CREATESTRUCT& cs) { + cs.lpszName = L"Taiga <3 Skype"; + cs.style = WS_OVERLAPPEDWINDOW; +} + +LRESULT Skype::Window::WindowProc(HWND hwnd, UINT uMsg, + WPARAM wParam, LPARAM lParam) { + if (::Skype.HandleMessage(uMsg, wParam, lParam)) + return TRUE; + + return WindowProcDefault(hwnd, uMsg, wParam, lParam); +} + +LRESULT Skype::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { + if (uMsg == WM_COPYDATA) { + if (hwnd_skype == nullptr || + hwnd_skype != reinterpret_cast(wParam)) + return FALSE; + + auto pCDS = reinterpret_cast(lParam); + std::wstring command = StrToWstr(reinterpret_cast(pCDS->lpData)); + LOG(LevelDebug, L"Received WM_COPYDATA: " + command); + + std::wstring profile_command = L"PROFILE RICH_MOOD_TEXT "; + if (StartsWith(command, profile_command)) { + std::wstring mood = command.substr(profile_command.length()); + if (mood != current_mood && mood != previous_mood) { + LOG(LevelDebug, L"Saved previous mood message: " + mood); + previous_mood = mood; + } + } + + return TRUE; + + } else if (uMsg == wm_attach) { + hwnd_skype = nullptr; + + switch (lParam) { + case kSkypeControlApiAttachSuccess: + LOG(LevelDebug, L"Attach succeeded."); + hwnd_skype = reinterpret_cast(wParam); + GetMoodText(); + if (!current_mood.empty()) + SetMoodText(current_mood); + break; + case kSkypeControlApiAttachPendingAuthorization: + LOG(LevelDebug, L"Waiting for user confirmation..."); + break; + case kSkypeControlApiAttachRefused: + LOG(LevelError, L"User denied access to client."); + break; + case kSkypeControlApiAttachNotAvailable: + LOG(LevelError, L"API is not available."); + break; + case kSkypeControlApiAttachApiAvailable: + LOG(LevelDebug, L"API is now available."); + Discover(); + break; + default: + LOG(LevelDebug, L"Received unknown message."); + break; + } + + return TRUE; + + } else if (uMsg == wm_discover) { + LOG(LevelDebug, L"Received SkypeControlAPIDiscover message."); + } + + return FALSE; +} + +void Announcer::ToSkype(const std::wstring& mood) { + ::Skype.current_mood = mood; + + if (::Skype.hwnd_skype == nullptr) { + ::Skype.Discover(); + } else { + ::Skype.SetMoodText(mood); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Twitter + +Twitter::Twitter() { + // These are unique values that identify Taiga + oauth.consumer_key = L"9GZsCbqzjOrsPWlIlysvg"; + oauth.consumer_secret = L"ebjXyymbuLtjDvoxle9Ldj8YYIMoleORapIOoqBrjRw"; +} + +bool Twitter::RequestToken() { + HttpRequest http_request; + http_request.url.protocol = base::http::kHttps; + http_request.url.host = L"api.twitter.com"; + http_request.url.path = L"/oauth/request_token"; + http_request.header[L"Authorization"] = + oauth.BuildAuthorizationHeader(http_request.url.Build(), L"GET"); + + ConnectionManager.MakeRequest(http_request, taiga::kHttpTwitterRequest); + return true; +} + +bool Twitter::AccessToken(const std::wstring& key, const std::wstring& secret, + const std::wstring& pin) { + HttpRequest http_request; + http_request.url.protocol = base::http::kHttps; + http_request.url.host = L"api.twitter.com"; + http_request.url.path = L"/oauth/access_token"; + http_request.header[L"Authorization"] = + oauth.BuildAuthorizationHeader(http_request.url.Build(), + L"POST", nullptr, key, secret, pin); + + ConnectionManager.MakeRequest(http_request, taiga::kHttpTwitterAuth); + return true; +} + +bool Twitter::SetStatusText(const std::wstring& status_text) { + if (Settings[kShare_Twitter_OauthToken].empty() || + Settings[kShare_Twitter_OauthSecret].empty()) + return false; + if (status_text.empty() || status_text == status_text_) + return false; + + status_text_ = status_text; + + oauth_parameter_t post_parameters; + post_parameters[L"status"] = EncodeUrl(status_text_); + + HttpRequest http_request; + http_request.method = L"POST"; + http_request.url.protocol = base::http::kHttps; + http_request.url.host = L"api.twitter.com"; + http_request.url.path = L"/1.1/statuses/update.json"; + http_request.body = L"status=" + post_parameters[L"status"]; + http_request.header[L"Authorization"] = + oauth.BuildAuthorizationHeader(http_request.url.Build(), + L"POST", &post_parameters, + Settings[kShare_Twitter_OauthToken], + Settings[kShare_Twitter_OauthSecret]); + + ConnectionManager.MakeRequest(http_request, taiga::kHttpTwitterPost); + return true; +} + +void Twitter::HandleHttpResponse(HttpClientMode mode, + const HttpResponse& response) { + switch (mode) { + case kHttpTwitterRequest: { + bool success = false; + oauth_parameter_t parameters = oauth.ParseQueryString(response.body); + if (!parameters[L"oauth_token"].empty()) { + ExecuteLink(L"http://api.twitter.com/oauth/authorize?oauth_token=" + + parameters[L"oauth_token"]); + string_t auth_pin; + if (ui::OnTwitterTokenEntry(auth_pin)) + AccessToken(parameters[L"oauth_token"], + parameters[L"oauth_token_secret"], + auth_pin); + success = true; + } + ui::OnTwitterTokenRequest(success); + break; + } + + case kHttpTwitterAuth: { + bool success = false; + oauth_parameter_t parameters = oauth.ParseQueryString(response.body); + if (!parameters[L"oauth_token"].empty() && + !parameters[L"oauth_token_secret"].empty()) { + Settings.Set(kShare_Twitter_OauthToken, parameters[L"oauth_token"]); + Settings.Set(kShare_Twitter_OauthSecret, parameters[L"oauth_token_secret"]); + Settings.Set(kShare_Twitter_Username, parameters[L"screen_name"]); + success = true; + } + ui::OnTwitterAuth(success); + break; + } + + case kHttpTwitterPost: { + if (InStr(response.body, L"\"errors\"", 0) == -1) { + ui::OnTwitterPost(true, L""); + } else { + string_t error; + int index_begin = InStr(response.body, L"\"message\":\"", 0); + int index_end = InStr(response.body, L"\",\"", index_begin); + if (index_begin > -1 && index_end > -1) { + index_begin += 11; + error = response.body.substr(index_begin, index_end - index_begin); + } + ui::OnTwitterPost(false, error); + } + break; + } + } +} + +void Announcer::ToTwitter(const std::wstring& status_text) { + ::Twitter.SetStatusText(status_text); +} + +} // namespace taiga \ No newline at end of file diff --git a/src/taiga/announce.h b/src/taiga/announce.h new file mode 100644 index 000000000..2e5f0eab6 --- /dev/null +++ b/src/taiga/announce.h @@ -0,0 +1,133 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TAIGA_ANNOUNCE_H +#define TAIGA_TAIGA_ANNOUNCE_H + +#include + +#include "base/oauth.h" +#include "base/types.h" +#include "taiga/http.h" +#include "win/win_window.h" + +namespace anime { +class Episode; +} + +namespace taiga { + +enum AnnouncerModes { + kAnnounceToHttp = 0x01, + kAnnounceToMirc = 0x02, + kAnnounceToSkype = 0x04, + kAnnounceToTwitter = 0x08 +}; + +class Announcer { +public: + void Clear(int modes, bool force = false); + void Do(int modes, anime::Episode* episode = nullptr, bool force = false); + + bool TestMircConnection(const std::wstring& service); + +private: + void ToHttp(const std::wstring& address, const std::wstring& data); + bool ToMirc(const std::wstring& service, std::wstring channels, const std::wstring& data, int mode, BOOL use_action, BOOL multi_server); + void ToSkype(const std::wstring& mood); + void ToTwitter(const std::wstring& status_text); +}; + +//////////////////////////////////////////////////////////////////////////////// +// mIRC + +enum MircChannelMode { + kMircChannelModeActive, + kMircChannelModeAll, + kMircChannelModeCustom +}; + +//////////////////////////////////////////////////////////////////////////////// +// Skype + +enum SkypeConnectionStatus { + kSkypeControlApiAttachSuccess, + kSkypeControlApiAttachPendingAuthorization, + kSkypeControlApiAttachRefused, + kSkypeControlApiAttachNotAvailable, + kSkypeControlApiAttachApiAvailable = 0x8001 +}; + +class Skype { +public: + Skype(); + virtual ~Skype(); + + void Create(); + BOOL Discover(); + LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam); + bool SendCommand(const std::wstring& command); + bool GetMoodText(); + bool SetMoodText(const std::wstring& mood); + + static const UINT wm_attach; + static const UINT wm_discover; + +public: + HWND hwnd, hwnd_skype; + std::wstring current_mood, previous_mood; + +private: + class Window : public win::Window { + private: + void PreRegisterClass(WNDCLASSEX& wc); + void PreCreate(CREATESTRUCT& cs); + LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + } window_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Twitter + +class Twitter { +public: + Twitter(); + ~Twitter() {} + + bool RequestToken(); + bool AccessToken(const std::wstring& key, const std::wstring& secret, const std::wstring& pin); + bool SetStatusText(const std::wstring& status_text); + + void HandleHttpResponse(HttpClientMode mode, const HttpResponse& response); + +public: + OAuth oauth; + +private: + std::wstring status_text_; +}; + +} // namespace taiga + +//////////////////////////////////////////////////////////////////////////////// + +extern taiga::Announcer Announcer; +extern taiga::Skype Skype; +extern taiga::Twitter Twitter; + +#endif // TAIGA_TAIGA_ANNOUNCE_H \ No newline at end of file diff --git a/api.cpp b/src/taiga/api.cpp similarity index 59% rename from api.cpp rename to src/taiga/api.cpp index 354590c56..fa45dd718 100644 --- a/api.cpp +++ b/src/taiga/api.cpp @@ -1,127 +1,132 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "api.h" - -#include "anime_episode.h" -#include "common.h" -#include "logger.h" -#include "string.h" - -class Api TaigaApi; - -// ============================================================================= - -Api::Api() { - // Register window messages - wm_attach = ::RegisterWindowMessage(L"TaigaApiAttach"); - wm_detach = ::RegisterWindowMessage(L"TaigaApiDetach"); - wm_ready = ::RegisterWindowMessage(L"TaigaApiReady"); - wm_quit = ::RegisterWindowMessage(L"TaigaApiQuit"); -} - -Api::~Api() { - // Destroy API window - TaigaApi.BroadcastMessage(TaigaApi.wm_quit); - window.Destroy(); -} - -void Api::Announce(anime::Episode& episode) { - for (auto it = handles.begin(); it != handles.end(); ++it) { - // Validate window - if (!::IsWindow(it->first)) { - it = handles.erase(it); - continue; - } - // Validate format - if (it->second.empty()) { - continue; - } - - const char* format = ToANSI(ReplaceVariables(it->second, episode)); - - COPYDATASTRUCT cds; - cds.dwData = 0; - cds.lpData = (void*)format; - cds.cbData = strlen(format) + 1; - - SendMessage(it->first, WM_COPYDATA, - reinterpret_cast(window.GetWindowHandle()), - reinterpret_cast(&cds)); - } -} - -void Api::BroadcastMessage(UINT message) { - PDWORD_PTR result = nullptr; - SendMessageTimeout(HWND_BROADCAST, message, - reinterpret_cast(window.GetWindowHandle()), - 0, SMTO_NORMAL, 100, result); -} - -void Api::Create() { - // Create API window - window.Create(); - TaigaApi.BroadcastMessage(TaigaApi.wm_ready); -} - -// ============================================================================= - -void Api::Window::PreRegisterClass(WNDCLASSEX& wc) { - wc.lpszClassName = L"TaigaApiW"; -} - -void Api::Window::PreCreate(CREATESTRUCT& cs) { - cs.lpszName = L"Taiga API"; - cs.style = WS_OVERLAPPEDWINDOW; -} - -LRESULT Api::Window::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - HWND hwnd_app = reinterpret_cast(wParam); - - // Attach a handle - if (uMsg == TaigaApi.wm_attach) { - TaigaApi.handles[hwnd_app] = L""; - LOG(LevelDebug, L"Attached handle: " + ToWstr(reinterpret_cast(hwnd_app))); - return TRUE; - } - // Detach a handle - if (uMsg == TaigaApi.wm_detach) { - auto it = TaigaApi.handles.find(hwnd_app); - if (it != TaigaApi.handles.end()) { - TaigaApi.handles.erase(it); - LOG(LevelDebug, L"Detached handle: " + ToWstr(reinterpret_cast(hwnd_app))); - return TRUE; - } else { - return FALSE; - } - } - - // Set announcement format - if (uMsg == WM_COPYDATA) { - auto cds = reinterpret_cast(lParam); - string format = reinterpret_cast(cds->lpData); - TaigaApi.handles[hwnd_app] = ToUTF8(format); - LOG(LevelDebug, L"New format for " + ToWstr(reinterpret_cast(hwnd_app)) + - L": \"" + TaigaApi.handles[hwnd_app] + L"\""); - return TRUE; - } - - return WindowProcDefault(hwnd, uMsg, wParam, lParam); -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "base/log.h" +#include "base/string.h" +#include "library/anime_episode.h" +#include "taiga/api.h" +#include "taiga/script.h" + +taiga::Api TaigaApi; + +namespace taiga { + +Api::Api() { + // Register window messages + wm_attach_ = ::RegisterWindowMessage(L"TaigaApiAttach"); + wm_detach_ = ::RegisterWindowMessage(L"TaigaApiDetach"); + wm_ready_ = ::RegisterWindowMessage(L"TaigaApiReady"); + wm_quit_ = ::RegisterWindowMessage(L"TaigaApiQuit"); +} + +Api::~Api() { + // Destroy API window + TaigaApi.BroadcastMessage(TaigaApi.wm_quit_); + window.Destroy(); +} + +void Api::Announce(anime::Episode& episode) { + foreach_(it, handles) { + // Validate window + if (!::IsWindow(it->first)) { + it = handles.erase(it); + continue; + } + // Validate format + if (it->second.empty()) { + continue; + } + + std::string str = WstrToStr(ReplaceVariables(it->second, episode)); + const char* format = str.c_str(); + + COPYDATASTRUCT cds; + cds.dwData = 0; + cds.lpData = (void*)format; + cds.cbData = strlen(format) + 1; + + SendMessage(it->first, WM_COPYDATA, + reinterpret_cast(window.GetWindowHandle()), + reinterpret_cast(&cds)); + } +} + +void Api::BroadcastMessage(UINT message) { + PDWORD_PTR result = nullptr; + SendMessageTimeout(HWND_BROADCAST, message, + reinterpret_cast(window.GetWindowHandle()), + 0, SMTO_NORMAL, 100, result); +} + +void Api::Create() { + // Create API window + window.Create(); + TaigaApi.BroadcastMessage(TaigaApi.wm_ready_); +} + +//////////////////////////////////////////////////////////////////////////////// + +void Api::Window::PreRegisterClass(WNDCLASSEX& wc) { + wc.lpszClassName = L"TaigaApiW"; +} + +void Api::Window::PreCreate(CREATESTRUCT& cs) { + cs.lpszName = L"Taiga API"; + cs.style = WS_OVERLAPPEDWINDOW; +} + +LRESULT Api::Window::WindowProc(HWND hwnd, UINT uMsg, + WPARAM wParam, LPARAM lParam) { + HWND hwnd_app = reinterpret_cast(wParam); + + // Attach a handle + if (uMsg == TaigaApi.wm_attach_) { + TaigaApi.handles[hwnd_app] = L""; + LOG(LevelDebug, L"Attached handle: " + + ToWstr(reinterpret_cast(hwnd_app))); + return TRUE; + } + // Detach a handle + if (uMsg == TaigaApi.wm_detach_) { + auto it = TaigaApi.handles.find(hwnd_app); + if (it != TaigaApi.handles.end()) { + TaigaApi.handles.erase(it); + LOG(LevelDebug, L"Detached handle: " + + ToWstr(reinterpret_cast(hwnd_app))); + return TRUE; + } else { + return FALSE; + } + } + + // Set announcement format + if (uMsg == WM_COPYDATA) { + auto cds = reinterpret_cast(lParam); + std::string format = reinterpret_cast(cds->lpData); + TaigaApi.handles[hwnd_app] = StrToWstr(format); + LOG(LevelDebug, L"New format for " + + ToWstr(reinterpret_cast(hwnd_app)) + + L": \"" + TaigaApi.handles[hwnd_app] + L"\""); + return TRUE; + } + + return WindowProcDefault(hwnd, uMsg, wParam, lParam); +} + +} // namespace taiga \ No newline at end of file diff --git a/api.h b/src/taiga/api.h similarity index 67% rename from api.h rename to src/taiga/api.h index eb126cc19..f7ace15ae 100644 --- a/api.h +++ b/src/taiga/api.h @@ -1,60 +1,62 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef TAIGA_API_H -#define TAIGA_API_H - -#include "std.h" - -#include "win32/win_window.h" - -namespace anime { -class Episode; -} - -class Api { -public: - Api(); - virtual ~Api(); - - void Announce(anime::Episode& episode); - void BroadcastMessage(UINT message); - void Create(); - -private: - std::map handles; - - UINT wm_attach; - UINT wm_detach; - UINT wm_ready; - UINT wm_quit; - - class Window : public win32::Window { - public: - Window() {} - virtual ~Window() {} - private: - void PreRegisterClass(WNDCLASSEX& wc); - void PreCreate(CREATESTRUCT& cs); - LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - } window; -}; - -extern class Api TaigaApi; - -#endif // TAIGA_API_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TAIGA_API_H +#define TAIGA_TAIGA_API_H + +#include +#include + +#include "win/win_window.h" + +namespace anime { +class Episode; +} + +namespace taiga { + +class Api { +public: + Api(); + ~Api(); + + void Announce(anime::Episode& episode); + void BroadcastMessage(UINT message); + void Create(); + +private: + std::map handles; + + UINT wm_attach_; + UINT wm_detach_; + UINT wm_ready_; + UINT wm_quit_; + + class Window : public win::Window { + private: + void PreRegisterClass(WNDCLASSEX& wc); + void PreCreate(CREATESTRUCT& cs); + LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + } window; +}; + +} // namespace taiga + +extern taiga::Api TaigaApi; + +#endif // TAIGA_TAIGA_API_H \ No newline at end of file diff --git a/src/taiga/debug.cpp b/src/taiga/debug.cpp new file mode 100644 index 000000000..709d76795 --- /dev/null +++ b/src/taiga/debug.cpp @@ -0,0 +1,89 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/string.h" +#include "library/anime_db.h" +#include "taiga/debug.h" +#include "ui/dlg/dlg_main.h" +#include "ui/dialog.h" + +namespace debug { + +Tester::Tester() + : frequency_(0.0), value_(0) { +} + +void Tester::Start() { + LARGE_INTEGER li; + + if (frequency_ == 0.0) { + ::QueryPerformanceFrequency(&li); + frequency_ = double(li.QuadPart) / 1000.0; + } + + ::QueryPerformanceCounter(&li); + value_ = li.QuadPart; +} + +void Tester::End(std::wstring str, bool display_result) { + LARGE_INTEGER li; + + ::QueryPerformanceCounter(&li); + double value = double(li.QuadPart - value_) / frequency_; + + if (display_result) { + str = ToWstr(value, 2) + L"ms | Text: [" + str + L"]"; + ui::DlgMain.SetText(str); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void Print(std::wstring text) { +#ifdef _DEBUG + ::OutputDebugString(text.c_str()); +#else + UNREFERENCED_PARAMETER(text); +#endif +} + +void Test() { + // Define variables + std::wstring str; + + // Start ticking + Tester test; + test.Start(); + + for (int i = 0; i < 10000; i++) { + // Do some tests here + // ___ + // {o,o} + // |)__) + // --"-"-- + // O RLY? + } + + // Debug recognition engine + ui::ShowDialog(ui::kDialogTestRecognition); + + // Show result + test.End(str, 0); +} + +} // namespace debug \ No newline at end of file diff --git a/debug.h b/src/taiga/debug.h similarity index 68% rename from debug.h rename to src/taiga/debug.h index 9b1203d2a..6bbd020fd 100644 --- a/debug.h +++ b/src/taiga/debug.h @@ -1,45 +1,43 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DEBUG_H -#define DEBUG_H - -#include "std.h" - -namespace debug { - -// ============================================================================= - -class Tester { - public: - Tester(); - - void Start(); - void End(wstring str, bool display_result); - - private: - double frequency_; - __int64 value_; -}; - -void Print(wstring text); -void Test(); - -} // namespace debug - -#endif // DEBUG_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TAIGA_DEBUG_H +#define TAIGA_TAIGA_DEBUG_H + +#include + +namespace debug { + +class Tester { + public: + Tester(); + + void Start(); + void End(std::wstring str, bool display_result); + + private: + double frequency_; + __int64 value_; +}; + +void Print(std::wstring text); +void Test(); + +} // namespace debug + +#endif // TAIGA_TAIGA_DEBUG_H \ No newline at end of file diff --git a/src/taiga/dummy.cpp b/src/taiga/dummy.cpp new file mode 100644 index 000000000..0757ea1a7 --- /dev/null +++ b/src/taiga/dummy.cpp @@ -0,0 +1,56 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "sync/service.h" +#include "taiga/dummy.h" +#include "track/recognition.h" + +namespace taiga { + +class DummyAnime DummyAnime; +class DummyEpisode DummyEpisode; + +void DummyAnime::Initialize() { + SetSource(sync::kMyAnimeList); + SetId(L"4224", sync::kTaiga); + SetId(L"4224", sync::kMyAnimeList); + SetId(L"toradora", sync::kHummingbird); + SetTitle(L"Toradora!"); + SetSynonyms(L"Tiger X Dragon"); + SetType(anime::kTv); + SetEpisodeCount(25); + SetEpisodeLength(24); + SetAiringStatus(anime::kFinishedAiring); + SetDateStart(Date(2008, 10, 01)); + SetDateEnd(Date(2009, 03, 25)); + SetImageUrl(L"http://cdn.myanimelist.net/images/anime/5/22125.jpg"); + AddtoUserList(); + SetMyLastWatchedEpisode(25); + SetMyScore(10); + SetMyStatus(anime::kCompleted); + SetMyTags(L"comedy, romance, drama"); +} + +void DummyEpisode::Initialize() { + anime_id = 74164; + folder = L"D:\\Anime\\"; + file = L"[TaigaSubs]_Toradora!_-_01v2_-_Tiger_and_Dragon_[DVD][1280x720_H264_AAC][ABCD1234].mkv"; + Meow.ExamineTitle(file, *this); +} + +} // namespace taiga \ No newline at end of file diff --git a/src/taiga/dummy.h b/src/taiga/dummy.h new file mode 100644 index 000000000..c67c128ef --- /dev/null +++ b/src/taiga/dummy.h @@ -0,0 +1,42 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TAIGA_DUMMY_H +#define TAIGA_TAIGA_DUMMY_H + +#include "library/anime_episode.h" +#include "library/anime_item.h" + +namespace taiga { + +class DummyAnime : public anime::Item { +public: + void Initialize(); +}; + +class DummyEpisode : public anime::Episode { +public: + void Initialize(); +}; + +extern class DummyAnime DummyAnime; +extern class DummyEpisode DummyEpisode; + +} // namespace taiga + +#endif // TAIGA_TAIGA_DUMMY_H \ No newline at end of file diff --git a/src/taiga/http.cpp b/src/taiga/http.cpp new file mode 100644 index 000000000..1d13c5c76 --- /dev/null +++ b/src/taiga/http.cpp @@ -0,0 +1,340 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "base/log.h" +#include "base/string.h" +#include "base/url.h" +#include "library/resource.h" +#include "sync/manager.h" +#include "taiga/announce.h" +#include "taiga/http.h" +#include "taiga/settings.h" +#include "taiga/stats.h" +#include "taiga/taiga.h" +#include "taiga/version.h" +#include "ui/ui.h" + +taiga::HttpManager ConnectionManager; + +namespace taiga { + +// These are the values commonly used by today's web browsers. +// See: http://www.browserscope.org/?category=network +const unsigned int kMaxSimultaneousConnections = 10; +const unsigned int kMaxSimultaneousConnectionsPerHostname = 6; + +HttpClient::HttpClient() + : mode_(kHttpSilent) { + // The default header (e.g. "User-Agent: Taiga/1.0") will be used, unless + // another value is specified in the request header + set_user_agent( + TAIGA_APP_NAME L"/" + + ToWstr(TAIGA_VERSION_MAJOR) + L"." + ToWstr(TAIGA_VERSION_MINOR)); + + // Make sure all new clients use the proxy settings + set_proxy(Settings[kApp_Connection_ProxyHost], + Settings[kApp_Connection_ProxyUsername], + Settings[kApp_Connection_ProxyPassword]); +} + +HttpClientMode HttpClient::mode() const { + return mode_; +} + +void HttpClient::set_mode(HttpClientMode mode) { + mode_ = mode; +} + +//////////////////////////////////////////////////////////////////////////////// + +void HttpClient::OnError(CURLcode error_code) { + std::wstring error_text = L"HTTP error #" + ToWstr(error_code) + L": " + + StrToWstr(curl_easy_strerror(error_code)); + TrimRight(error_text, L"\r\n"); + + LOG(LevelError, error_text); + LOG(LevelError, L"Connection mode: " + ToWstr(mode_)); + + ui::OnHttpError(*this, error_text); + + Stats.connections_failed++; + + ConnectionManager.HandleError(response_, error_text); +} + +bool HttpClient::OnHeadersAvailable() { + ui::OnHttpHeadersAvailable(*this); + return false; +} + +bool HttpClient::OnRedirect(const std::wstring& address) { + LOG(LevelDebug, L"Redirecting... (" + address + L")"); + + switch (mode()) { + case kHttpTaigaUpdateDownload: { + std::wstring file = address.substr(address.find_last_of(L"/") + 1); + download_path_ = GetPathOnly(download_path_) + file; + Taiga.Updater.SetDownloadPath(download_path_); + break; + } + } + + Url url(address); + ConnectionManager.HandleRedirect(request_.url.host, url.host); + + return false; +} + +bool HttpClient::OnProgress() { + ui::OnHttpProgress(*this); + return false; +} + +void HttpClient::OnReadComplete() { + ui::OnHttpReadComplete(*this); + + Stats.connections_succeeded++; + + ConnectionManager.HandleResponse(response_); +} + +//////////////////////////////////////////////////////////////////////////////// + +HttpClient& HttpManager::GetClient(HttpRequest& request) { + HttpClient* client = nullptr; + + foreach_(it, clients_) { + if (it->second.allow_reuse() && !it->second.busy()) { + if (IsEqual(it->second.request().url.host, request.url.host)) { + LOG(LevelDebug, L"Will reuse client with the ID: " + it->first); + const_cast(it->first) = request.uid; + LOG(LevelDebug, L"Client's new ID: " + it->first); + client = &it->second; + break; + } + } + } + + if (!client) + client = &clients_[request.uid]; + + return *client; +} + +void HttpManager::CancelRequest(base::uid_t uid) { + if (clients_.count(uid)) { + auto& client = clients_[uid]; + if (client.busy()) + client.Cancel(); + } +} + +void HttpManager::MakeRequest(HttpRequest& request, HttpClientMode mode) { + if (clients_.count(request.uid)) { + LOG(LevelWarning, L"HttpClient already exists. ID: " + request.uid); + } + + HttpClient& client = GetClient(request); + client.set_mode(mode); + + AddToQueue(request); + ProcessQueue(); +} + +void HttpManager::MakeRequest(HttpClient& client, HttpRequest& request, + HttpClientMode mode) { + client.set_mode(mode); + + AddToQueue(request); + ProcessQueue(); +} + +void HttpManager::HandleError(HttpResponse& response, const string_t& error) { + HttpClient& client = clients_[response.uid]; + + switch (client.mode()) { + case kHttpServiceAuthenticateUser: + case kHttpServiceGetMetadataById: + case kHttpServiceGetMetadataByIdV2: + case kHttpServiceSearchTitle: + case kHttpServiceAddLibraryEntry: + case kHttpServiceDeleteLibraryEntry: + case kHttpServiceGetLibraryEntries: + case kHttpServiceUpdateLibraryEntry: + ServiceManager.HandleHttpError(client.response_, error); + break; + } + + FreeConnection(client.request_.url.host); + ProcessQueue(); +} + +void HttpManager::HandleRedirect(const std::wstring& current_host, + const std::wstring& next_host) { + FreeConnection(current_host); + AddConnection(next_host); +} + +void HttpManager::HandleResponse(HttpResponse& response) { + HttpClient& client = clients_[response.uid]; + + switch (client.mode()) { + case kHttpServiceAuthenticateUser: + case kHttpServiceGetMetadataById: + case kHttpServiceGetMetadataByIdV2: + case kHttpServiceSearchTitle: + case kHttpServiceAddLibraryEntry: + case kHttpServiceDeleteLibraryEntry: + case kHttpServiceGetLibraryEntries: + case kHttpServiceUpdateLibraryEntry: + ServiceManager.HandleHttpResponse(response); + break; + + case kHttpGetLibraryEntryImage: { + int anime_id = static_cast(response.parameter); + if (ImageDatabase.Load(anime_id, true, false)) + ui::OnLibraryEntryImageChange(anime_id); + break; + } + + case kHttpFeedCheck: + case kHttpFeedCheckAuto: { + Feed* feed = reinterpret_cast(response.parameter); + if (feed) { + bool automatic = client.mode() == kHttpFeedCheckAuto; + Aggregator.HandleFeedCheck(*feed, automatic); + } + break; + } + case kHttpFeedDownload: + case kHttpFeedDownloadAll: { + auto feed = reinterpret_cast(response.parameter); + if (feed) { + bool download_all = client.mode() == kHttpFeedDownloadAll; + Aggregator.HandleFeedDownload(*feed, download_all); + } + break; + } + + case kHttpTwitterRequest: + case kHttpTwitterAuth: + case kHttpTwitterPost: + ::Twitter.HandleHttpResponse(client.mode(), response); + break; + + case kHttpTaigaUpdateCheck: + if (Taiga.Updater.ParseData(response.body)) + if (Taiga.Updater.IsDownloadAllowed()) + break; + ui::OnUpdateFinished(); + break; + case kHttpTaigaUpdateDownload: + Taiga.Updater.RunInstaller(); + ui::OnUpdateFinished(); + break; + } + + FreeConnection(client.request_.url.host); + ProcessQueue(); +} + +//////////////////////////////////////////////////////////////////////////////// + +void HttpManager::FreeMemory() { + for (auto it = clients_.cbegin(); it != clients_.cend(); ) { + if (!it->second.busy()) { + clients_.erase(it++); + } else { + ++it; + } + } +} + +void HttpManager::Shutdown() { + clients_.clear(); +} + +//////////////////////////////////////////////////////////////////////////////// + +void HttpManager::AddToQueue(HttpRequest& request) { +#ifdef TAIGA_HTTP_MULTITHREADED + win::Lock lock(critical_section_); + + LOG(LevelDebug, L"ID: " + request.uid); + + requests_.push_back(request); +#else + HttpClient& client = clients_[request.uid]; + client.MakeRequest(request); +#endif +} + +void HttpManager::ProcessQueue() { +#ifdef TAIGA_HTTP_MULTITHREADED + win::Lock lock(critical_section_); + + unsigned int connections = 0; + foreach_(it, connections_) + connections += it->second; + + for (size_t i = 0; i < requests_.size(); i++) { + if (connections == kMaxSimultaneousConnections) { + LOG(LevelDebug, L"Reached max connections"); + break; + } + + HttpRequest& request = requests_.at(i); + if (connections_[request.url.host] == kMaxSimultaneousConnectionsPerHostname) { + LOG(LevelDebug, L"Reached max connections for hostname: " + request.url.host); + continue; + } else { + connections++; + connections_[request.url.host]++; + + HttpClient& client = clients_[request.uid]; + client.MakeRequest(request); + + requests_.erase(requests_.begin() + i); + i--; + } + } +#endif +} + +void HttpManager::AddConnection(const string_t& hostname) { +#ifdef TAIGA_HTTP_MULTITHREADED + win::Lock lock(critical_section_); + + connections_[hostname]++; +#endif +} + +void HttpManager::FreeConnection(const string_t& hostname) { +#ifdef TAIGA_HTTP_MULTITHREADED + win::Lock lock(critical_section_); + + if (connections_[hostname] > 0) { + connections_[hostname]--; + } else { + LOG(LevelError, L"Connections for hostname was already zero: " + hostname); + } +#endif +} + +} // namespace taiga \ No newline at end of file diff --git a/src/taiga/http.h b/src/taiga/http.h new file mode 100644 index 000000000..f5928dc17 --- /dev/null +++ b/src/taiga/http.h @@ -0,0 +1,109 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TAIGA_HTTP_H +#define TAIGA_TAIGA_HTTP_H + +#include + +#include "base/http.h" +#include "base/types.h" +#include "win/win_thread.h" + +namespace taiga { + +enum HttpClientMode { + kHttpSilent, + // Service + kHttpServiceAuthenticateUser, + kHttpServiceGetMetadataById, + kHttpServiceGetMetadataByIdV2, + kHttpServiceSearchTitle, + kHttpServiceAddLibraryEntry, + kHttpServiceDeleteLibraryEntry, + kHttpServiceGetLibraryEntries, + kHttpServiceUpdateLibraryEntry, + // Library + kHttpGetLibraryEntryImage, + // Feed + kHttpFeedCheck, + kHttpFeedCheckAuto, + kHttpFeedDownload, + kHttpFeedDownloadAll, + // Twitter + kHttpTwitterRequest, + kHttpTwitterAuth, + kHttpTwitterPost, + // Taiga + kHttpTaigaUpdateCheck, + kHttpTaigaUpdateDownload +}; + +class HttpClient : public base::http::Client { +public: + friend class HttpManager; + + HttpClient(); + virtual ~HttpClient() {} + + HttpClientMode mode() const; + void set_mode(HttpClientMode mode); + +protected: + void OnError(CURLcode error_code); + bool OnHeadersAvailable(); + bool OnProgress(); + void OnReadComplete(); + bool OnRedirect(const std::wstring& address); + +private: + HttpClientMode mode_; +}; + +class HttpManager { +public: + HttpClient& GetClient(HttpRequest& request); + + void CancelRequest(base::uid_t uid); + void MakeRequest(HttpRequest& request, HttpClientMode mode); + void MakeRequest(HttpClient& client, HttpRequest& request, HttpClientMode mode); + + void HandleError(HttpResponse& response, const string_t& error); + void HandleRedirect(const std::wstring& current_host, const std::wstring& next_host); + void HandleResponse(HttpResponse& response); + + void FreeMemory(); + void Shutdown(); + +private: + void AddToQueue(HttpRequest& request); + void ProcessQueue(); + void AddConnection(const string_t& hostname); + void FreeConnection(const string_t& hostname); + + std::map clients_; + std::map connections_; + win::CriticalSection critical_section_; + std::vector requests_; +}; + +} // namespace taiga + +extern taiga::HttpManager ConnectionManager; + +#endif // TAIGA_TAIGA_HTTP_H \ No newline at end of file diff --git a/src/taiga/path.cpp b/src/taiga/path.cpp new file mode 100644 index 000000000..03e0a8524 --- /dev/null +++ b/src/taiga/path.cpp @@ -0,0 +1,88 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include + +#include "base/string.h" +#include "sync/manager.h" +#include "taiga/path.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" + +namespace taiga { + +std::wstring GetDataPath() { +#ifdef TAIGA_PORTABLE + // Return current path in portable mode + return AddTrailingSlash(GetPathOnly(Taiga.GetModulePath())) + L"data\\"; +#else + // Return %AppData% folder + WCHAR buffer[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_APPDATA | CSIDL_FLAG_CREATE, + nullptr, SHGFP_TYPE_CURRENT, buffer))) + return AddTrailingSlash(buffer) + TAIGA_APP_NAME + L"\\"; +#endif +} + +std::wstring GetUserDirectoryName() { + std::wstring username = GetCurrentUsername(); + auto service = GetCurrentService(); + return username + L"@" + service->canonical_name(); +} + +std::wstring GetPath(PathType type) { + static const std::wstring data_path = GetDataPath(); + + switch (type) { + default: + case kPathData: + return data_path; + case kPathDatabase: + return data_path + L"db\\"; + case kPathDatabaseAnime: + return data_path + L"db\\anime.xml"; + case kPathDatabaseImage: + return data_path + L"db\\image\\"; + case kPathDatabaseSeason: + return data_path + L"db\\season\\"; + case kPathFeed: + return data_path + L"feed\\"; + case kPathFeedHistory: + return data_path + L"feed\\history.xml"; + case kPathMedia: + return data_path + L"media.xml"; + case kPathSettings: + return data_path + L"settings.xml"; + case kPathTest: + return data_path + L"test\\"; + case kPathTestRecognition: + return data_path + L"test\\recognition.xml"; + case kPathTheme: + return data_path + L"theme\\"; + case kPathThemeCurrent: + return data_path + L"theme\\" + Settings[kApp_Interface_Theme] + L"\\theme.xml"; + case kPathUser: + return data_path + L"user\\"; + case kPathUserHistory: + return data_path + L"user\\" + GetUserDirectoryName() + L"\\history.xml"; + case kPathUserLibrary: + return data_path + L"user\\" + GetUserDirectoryName() + L"\\anime.xml"; + } +} + +} // namespace taiga \ No newline at end of file diff --git a/src/taiga/path.h b/src/taiga/path.h new file mode 100644 index 000000000..96f9d1ab6 --- /dev/null +++ b/src/taiga/path.h @@ -0,0 +1,49 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TAIGA_PATH_H +#define TAIGA_TAIGA_PATH_H + +#include + +namespace taiga { + +enum PathType { + kPathData, + kPathDatabase, + kPathDatabaseAnime, + kPathDatabaseImage, + kPathDatabaseSeason, + kPathFeed, + kPathFeedHistory, + kPathMedia, + kPathSettings, + kPathTest, + kPathTestRecognition, + kPathTheme, + kPathThemeCurrent, + kPathUser, + kPathUserHistory, + kPathUserLibrary +}; + +std::wstring GetPath(PathType type); + +} // namespace taiga + +#endif // TAIGA_TAIGA_PATH_H \ No newline at end of file diff --git a/src/taiga/resource.h b/src/taiga/resource.h new file mode 100644 index 000000000..fde209b2f --- /dev/null +++ b/src/taiga/resource.h @@ -0,0 +1,208 @@ +#ifndef IDC_STATIC +#define IDC_STATIC (-1) +#endif + +#define IDI_MAIN 101 +#define IDD_ABOUT 110 +#define IDD_ANIME_INFO 111 +#define IDD_ANIME_INFO_PAGE01 112 +#define IDD_ANIME_INFO_PAGE02 113 +#define IDD_ANIME_LIST 114 +#define IDD_FEED_CONDITION 115 +#define IDD_FEED_FILTER 116 +#define IDD_FEED_FILTER_PAGE1 117 +#define IDD_FEED_FILTER_PAGE2 118 +#define IDD_FEED_FILTER_PAGE3 119 +#define IDD_FORMAT 120 +#define IDD_HISTORY 121 +#define IDD_INPUT 122 +#define IDD_MAIN 123 +#define IDD_SEARCH 124 +#define IDD_SEASON 125 +#define IDD_SETTINGS 126 +#define IDD_SETTINGS_APP_BEHAVIOR 127 +#define IDD_SETTINGS_APP_CONNECTION 128 +#define IDD_SETTINGS_APP_INTERFACE 129 +#define IDD_SETTINGS_APP_LIST 130 +#define IDD_SETTINGS_LIBRARY_CACHE 131 +#define IDD_SETTINGS_LIBRARY_FOLDERS 132 +#define IDD_SETTINGS_RECOGNITION_GENERAL 133 +#define IDD_SETTINGS_RECOGNITION_MEDIA 134 +#define IDD_SETTINGS_RECOGNITION_STREAM 135 +#define IDD_SETTINGS_SERVICES_MAIN 136 +#define IDD_SETTINGS_SERVICES_MAL 137 +#define IDD_SETTINGS_SERVICES_HUMMINGBIRD 138 +#define IDD_SETTINGS_SHARING_HTTP 139 +#define IDD_SETTINGS_SHARING_MIRC 141 +#define IDD_SETTINGS_SHARING_SKYPE 142 +#define IDD_SETTINGS_SHARING_TWITTER 143 +#define IDD_SETTINGS_TORRENTS_DISCOVERY 144 +#define IDD_SETTINGS_TORRENTS_DOWNLOADS 145 +#define IDD_SETTINGS_TORRENTS_FILTERS 146 +#define IDD_STATS 147 +#define IDD_TEST_RECOGNITION 148 +#define IDD_TORRENT 149 +#define IDD_UPDATE 150 +#define IDD_UPDATE_NEW 151 +#define IDC_BUTTON_ADDFOLDER 1000 +#define IDC_BUTTON_BROWSE 1001 +#define IDC_BUTTON_CACHE_CLEAR 1002 +#define IDC_BUTTON_CANCELSEARCH 1003 +#define IDC_BUTTON_FORMAT_BALLOON 1004 +#define IDC_BUTTON_FORMAT_HTTP 1005 +#define IDC_BUTTON_FORMAT_MIRC 1006 +#define IDC_BUTTON_FORMAT_SKYPE 1008 +#define IDC_BUTTON_FORMAT_TWITTER 1009 +#define IDC_BUTTON_MIRC_TEST 1010 +#define IDC_BUTTON_REMOVEFOLDER 1011 +#define IDC_BUTTON_TORRENT_BROWSE_APP 1012 +#define IDC_BUTTON_TORRENT_BROWSE_FOLDER 1013 +#define IDC_BUTTON_TWITTER_AUTH 1014 +#define IDC_CHECK_ANIME_ALT 1015 +#define IDC_CHECK_ANIME_REWATCH 1016 +#define IDC_CHECK_AUTOSTART 1017 +#define IDC_CHECK_CACHE1 1018 +#define IDC_CHECK_CACHE2 1019 +#define IDC_CHECK_CACHE3 1020 +#define IDC_CHECK_FOLDERS_WATCH 1021 +#define IDC_CHECK_GENERAL_CLOSE 1022 +#define IDC_CHECK_GENERAL_MINIMIZE 1023 +#define IDC_CHECK_HIGHLIGHT 1024 +#define IDC_CHECK_HTTP 1025 +#define IDC_CHECK_LIST_ENGLISH 1026 +#define IDC_CHECK_LIST_PROGRESS_AIRED 1027 +#define IDC_CHECK_LIST_PROGRESS_AVAILABLE 1028 +#define IDC_CHECK_MIRC 1030 +#define IDC_CHECK_MIRC_ACTION 1031 +#define IDC_CHECK_MIRC_MULTISERVER 1032 +#define IDC_CHECK_NOTIFY_NOTRECOGNIZED 1033 +#define IDC_CHECK_NOTIFY_RECOGNIZED 1034 +#define IDC_CHECK_SKYPE 1035 +#define IDC_CHECK_START_CHECKEPS 1036 +#define IDC_CHECK_START_LOGIN 1037 +#define IDC_CHECK_START_MINIMIZE 1038 +#define IDC_CHECK_START_VERSION 1039 +#define IDC_CHECK_TORRENT_AUTOCHECK 1040 +#define IDC_CHECK_TORRENT_AUTOCREATEFOLDER 1041 +#define IDC_CHECK_TORRENT_AUTOSETFOLDER 1042 +#define IDC_CHECK_TORRENT_AUTOUSEFOLDER 1043 +#define IDC_CHECK_TORRENT_FILTER 1044 +#define IDC_CHECK_TWITTER 1045 +#define IDC_CHECK_UPDATE_CHECKMP 1046 +#define IDC_CHECK_UPDATE_CONFIRM 1047 +#define IDC_CHECK_UPDATE_GOTO 1048 +#define IDC_CHECK_UPDATE_RANGE 1049 +#define IDC_CHECK_UPDATE_ROOT 1050 +#define IDC_CHECK_UPDATE_WAITMP 1051 +#define IDC_COMBO_ANIME_SCORE 1052 +#define IDC_COMBO_ANIME_STATUS 1053 +#define IDC_COMBO_DBLCLICK 1054 +#define IDC_COMBO_FEED_ELEMENT 1055 +#define IDC_COMBO_FEED_FILTER_ACTION 1056 +#define IDC_COMBO_FEED_FILTER_MATCH 1057 +#define IDC_COMBO_FEED_FILTER_OPTION 1058 +#define IDC_COMBO_FEED_OPERATOR 1059 +#define IDC_COMBO_FEED_VALUE 1060 +#define IDC_COMBO_MDLCLICK 1061 +#define IDC_COMBO_SERVICE 1062 +#define IDC_COMBO_THEME 1063 +#define IDC_COMBO_TORRENT_FOLDER 1064 +#define IDC_COMBO_TORRENT_SEARCH 1065 +#define IDC_COMBO_TORRENT_SOURCE 1066 +#define IDC_DATETIME_FINISH 1067 +#define IDC_DATETIME_START 1068 +#define IDC_EDIT_ANIME_ALT 1069 +#define IDC_EDIT_ANIME_FOLDER 1070 +#define IDC_EDIT_ANIME_PROGRESS 1071 +#define IDC_EDIT_ANIME_SYNOPSIS 1072 +#define IDC_EDIT_ANIME_TAGS 1073 +#define IDC_EDIT_ANIME_TITLE 1074 +#define IDC_EDIT_DELAY 1075 +#define IDC_EDIT_EXTERNALLINKS 1076 +#define IDC_EDIT_FEED_NAME 1077 +#define IDC_EDIT_HTTP_URL 1078 +#define IDC_EDIT_INPUT 1079 +#define IDC_EDIT_MIRC_CHANNELS 1080 +#define IDC_EDIT_MIRC_SERVICE 1081 +#define IDC_EDIT_PASS_HUMMINGBIRD 1082 +#define IDC_EDIT_PASS_MAL 1083 +#define IDC_EDIT_PREVIEW 1084 +#define IDC_EDIT_PROXY_HOST 1085 +#define IDC_EDIT_PROXY_PASS 1086 +#define IDC_EDIT_PROXY_USER 1087 +#define IDC_EDIT_SEARCH 1088 +#define IDC_EDIT_TORRENT_APP 1089 +#define IDC_EDIT_TORRENT_INTERVAL 1090 +#define IDC_EDIT_USER_HUMMINGBIRD 1091 +#define IDC_EDIT_USER_MAL 1092 +#define IDC_LINK_ACCOUNT_HUMMINGBIRD 1093 +#define IDC_LINK_ACCOUNT_MAL 1094 +#define IDC_LINK_ANIME_FANSUB 1095 +#define IDC_LINK_DEFAULTS 1096 +#define IDC_LINK_NOWPLAYING 1097 +#define IDC_LINK_THEMES 1098 +#define IDC_LINK_TWITTER 1099 +#define IDC_LIST_EVENT 1100 +#define IDC_LIST_FEED_FILTER_ANIME 1101 +#define IDC_LIST_FEED_FILTER_CONDITIONS 1102 +#define IDC_LIST_FEED_FILTER_PRESETS 1103 +#define IDC_LIST_FOLDERS_ROOT 1104 +#define IDC_LIST_MAIN 1105 +#define IDC_LIST_MEDIA 1106 +#define IDC_LIST_SEARCH 1107 +#define IDC_LIST_SEASON 1108 +#define IDC_LIST_STREAM_PROVIDER 1109 +#define IDC_LIST_TEST_RECOGNITION 1110 +#define IDC_LIST_TORRENT 1111 +#define IDC_LIST_TORRENT_FILTER 1112 +#define IDC_PROGRESS_UPDATE 1113 +#define IDC_RADIO_MIRC_CHANNEL1 1114 +#define IDC_RADIO_MIRC_CHANNEL2 1115 +#define IDC_RADIO_MIRC_CHANNEL3 1116 +#define IDC_RADIO_TORRENT_APP1 1117 +#define IDC_RADIO_TORRENT_APP2 1118 +#define IDC_RADIO_TORRENT_NEW1 1119 +#define IDC_RADIO_TORRENT_NEW2 1120 +#define IDC_REBAR_MAIN 1121 +#define IDC_REBAR_SEASON 1122 +#define IDC_REBAR_TORRENT 1123 +#define IDC_RICHEDIT_ABOUT 1124 +#define IDC_RICHEDIT_FORMAT 1125 +#define IDC_RICHEDIT_UPDATE 1126 +#define IDC_SPIN_DELAY 1127 +#define IDC_SPIN_INPUT 1128 +#define IDC_SPIN_PROGRESS 1129 +#define IDC_SPIN_TORRENT_INTERVAL 1130 +#define IDC_STATIC_ANIME_DETAILS 1131 +#define IDC_STATIC_ANIME_IMG 1132 +#define IDC_STATIC_ANIME_STAT1 1133 +#define IDC_STATIC_ANIME_STAT2 1134 +#define IDC_STATIC_ANIME_STAT3 1135 +#define IDC_STATIC_ANIME_STAT4 1136 +#define IDC_STATIC_CACHE1 1137 +#define IDC_STATIC_CACHE2 1138 +#define IDC_STATIC_CACHE3 1139 +#define IDC_STATIC_FEED_FILTER_DISCARDTYPE 1140 +#define IDC_STATIC_FEED_FILTER_LIMIT 1141 +#define IDC_STATIC_HEADER 1142 +#define IDC_STATIC_HEADER1 1143 +#define IDC_STATIC_HEADER2 1144 +#define IDC_STATIC_HEADER3 1145 +#define IDC_STATIC_HEADER4 1146 +#define IDC_STATIC_INPUTINFO 1147 +#define IDC_STATIC_TITLE 1148 +#define IDC_STATIC_UPDATE_DETAILS 1149 +#define IDC_STATIC_UPDATE_PROGRESS 1150 +#define IDC_STATIC_UPDATE_TITLE 1151 +#define IDC_STATUSBAR_MAIN 1152 +#define IDC_TAB_ANIME 1153 +#define IDC_TAB_MAIN 1154 +#define IDC_TAB_PAGES 1155 +#define IDC_TOOLBAR_FEED_FILTER 1156 +#define IDC_TOOLBAR_MAIN 1157 +#define IDC_TOOLBAR_MENU 1158 +#define IDC_TOOLBAR_SEARCH 1159 +#define IDC_TOOLBAR_SEASON 1160 +#define IDC_TOOLBAR_TORRENT 1161 +#define IDC_TREE_MAIN 1162 +#define IDC_TREE_SECTIONS 1163 diff --git a/resource.rc b/src/taiga/resource.rc similarity index 62% rename from resource.rc rename to src/taiga/resource.rc index ced8d6daa..ded337b31 100644 --- a/resource.rc +++ b/src/taiga/resource.rc @@ -1,724 +1,724 @@ -// Generated by ResEdit 1.5.11 -// Copyright (C) 2006-2012 -// http://www.resedit.net - -#include -#include -#include -#include "resource.h" - - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDR_MENU DATA ".\\res\\menu.xml" - - - -// -// Dialog resources -// -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_ABOUT DIALOGEX 0, 0, 365, 219 -STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU -EXSTYLE WS_EX_CONTROLPARENT -CAPTION "About" -CLASS "TaigaAboutW" -FONT 9, "Segoe UI", 400, 0, 0 -{ - ICON IDI_MAIN, 0, 7, 7, 27, 26, SS_ICON | SS_NOTIFY | SS_REALSIZEIMAGE - CONTROL "", IDC_RICHEDIT_ABOUT, RICHEDIT_CLASS, WS_VSCROLL | NOT WS_BORDER | NOT WS_TABSTOP | ES_AUTOVSCROLL | ES_MULTILINE | ES_READONLY, 48, 7, 310, 205 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_ANIME_INFO DIALOG 0, 0, 480, 310 -STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_SYSMENU -EXSTYLE WS_EX_WINDOWEDGE -CAPTION "Anime Information" -FONT 9, "Segoe UI" -{ - CONTROL "", IDC_STATIC_ANIME_IMG, WC_STATIC, SS_OWNERDRAW | SS_NOTIFY, 7, 7, 95, 120 - EDITTEXT IDC_EDIT_ANIME_TITLE, 105, 4, 360, 15, NOT WS_BORDER | NOT WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY - CONTROL "", IDC_TAB_ANIME, WC_TABCONTROL, 0, 106, 26, 360, 250 - CONTROL "", IDC_LINK_NOWPLAYING, "SysLink", 0x50010000, 7, 163, 95, 140 - DEFPUSHBUTTON "OK", IDOK, 369, 289, 50, 14 - PUSHBUTTON "Cancel", IDCANCEL, 423, 289, 50, 14 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_ANIME_INFO_PAGE01 DIALOGEX 0, 0, 355, 240 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - LTEXT "Alternative titles", IDC_STATIC_HEADER1, 0, 0, 340, 8, SS_LEFT - EDITTEXT IDC_EDIT_ANIME_ALT, 5, 15, 340, 12, NOT WS_BORDER | NOT WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY - LTEXT "Details", IDC_STATIC_HEADER2, 0, 30, 340, 8, SS_LEFT - LTEXT "Type:\nEpisodes:\nStatus:\nSeason:\nGenres:\nProducers:\nScore:\nPopularity:", IDC_STATIC, 7, 45, 50, 70, SS_LEFT - CONTROL "TV\n0\nFinished Airing\n2011 Winter\nAction, Adventure\nUnknown\n0.00\n#0", IDC_STATIC_ANIME_DETAILS, WC_STATIC, NOT WS_GROUP | SS_LEFTNOWORDWRAP | SS_NOPREFIX, 57, 45, 285, 70 - LTEXT "Synopsis", IDC_STATIC_HEADER3, 0, 115, 340, 8, SS_LEFT - EDITTEXT IDC_EDIT_ANIME_SYNOPSIS, 5, 130, 340, 95, NOT WS_BORDER | NOT WS_TABSTOP | ES_AUTOVSCROLL | ES_MULTILINE | ES_READONLY -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_ANIME_INFO_PAGE02 DIALOGEX 0, 0, 355, 240 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - LTEXT "My list", IDC_STATIC_HEADER1, 0, 0, 340, 8, SS_LEFT - LTEXT "Episodes watched:", IDC_STATIC, 7, 15, 100, 8, SS_LEFT - EDITTEXT IDC_EDIT_ANIME_PROGRESS, 7, 25, 100, 14, ES_AUTOHSCROLL | ES_NUMBER - CONTROL "", IDC_SPIN_PROGRESS, UPDOWN_CLASS, UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY | UDS_SETBUDDYINT, 97, 25, 12, 14 - AUTOCHECKBOX "Re-watching", IDC_CHECK_ANIME_REWATCH, 122, 26, 75, 10 - LTEXT "Status:", IDC_STATIC, 7, 45, 100, 8, SS_LEFT - COMBOBOX IDC_COMBO_ANIME_STATUS, 7, 55, 100, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS - LTEXT "Score:", IDC_STATIC, 122, 45, 100, 8, SS_LEFT - COMBOBOX IDC_COMBO_ANIME_SCORE, 122, 55, 100, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS - LTEXT "Tags:", IDC_STATIC, 7, 75, 60, 8, SS_LEFT - EDITTEXT IDC_EDIT_ANIME_TAGS, 7, 85, 341, 14, ES_AUTOHSCROLL - LTEXT "Start date:", IDC_STATIC, 7, 105, 90, 8, SS_LEFT - CONTROL "", IDC_DATETIME_START, DATETIMEPICK_CLASS, WS_TABSTOP | DTS_SHOWNONE | DTS_RIGHTALIGN, 7, 115, 100, 14 - LTEXT "Finish date:", IDC_STATIC, 122, 105, 90, 8, SS_LEFT - CONTROL "", IDC_DATETIME_FINISH, DATETIMEPICK_CLASS, WS_TABSTOP | DTS_SHOWNONE | DTS_RIGHTALIGN, 122, 115, 100, 14 - LTEXT "My settings", IDC_STATIC_HEADER2, 0, 136, 340, 8, SS_LEFT - LTEXT "Alternative titles:", IDC_STATIC, 7, 151, 85, 8, SS_LEFT - EDITTEXT IDC_EDIT_ANIME_ALT, 7, 161, 341, 14, ES_AUTOHSCROLL - AUTOCHECKBOX "Use the first alternative title to search for torrents", IDC_CHECK_ANIME_ALT, 7, 179, 215, 8 - LTEXT "Folder:", IDC_STATIC, 7, 194, 85, 8, SS_LEFT - EDITTEXT IDC_EDIT_ANIME_FOLDER, 7, 204, 322, 14, ES_AUTOHSCROLL - PUSHBUTTON "...", IDC_BUTTON_BROWSE, 333, 204, 15, 14 - CONTROL "Fansub group preference: None (Change)", IDC_LINK_ANIME_FANSUB, "SysLink", 0x50010000, 7, 224, 335, 10 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_ANIME_LIST DIALOGEX 0, 0, 460, 355 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW | WS_CLIPCHILDREN -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - CONTROL "", IDC_TAB_MAIN, WC_TABCONTROL, WS_TABSTOP | WS_CLIPSIBLINGS, 5, 5, 450, 345 - CONTROL "", IDC_LIST_MAIN, WC_LISTVIEW, WS_TABSTOP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | LVS_ALIGNLEFT | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 5, 5, 448, 330 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_FEED_CONDITION DIALOGEX 0, 0, 360, 66 -STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU -CAPTION "Add Condition" -CLASS "TaigaFeedConditionW" -FONT 9, "Segoe UI", 400, 0, 0 -{ - LTEXT "Element:", IDC_STATIC, 7, 7, 120, 8, SS_LEFT - COMBOBOX IDC_COMBO_FEED_ELEMENT, 7, 17, 120, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS - LTEXT "Operator:", IDC_STATIC, 134, 7, 92, 8, SS_LEFT - COMBOBOX IDC_COMBO_FEED_OPERATOR, 134, 17, 92, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS - LTEXT "Value:", IDC_STATIC, 233, 7, 120, 8, SS_LEFT - COMBOBOX IDC_COMBO_FEED_VALUE, 233, 17, 120, 30, WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWN | CBS_HASSTRINGS - DEFPUSHBUTTON "OK", IDOK, 249, 45, 50, 14 - PUSHBUTTON "Cancel", IDCANCEL, 303, 45, 50, 14 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_FEED_FILTER DIALOGEX 0, 0, 430, 285 -STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP | WS_SYSMENU -EXSTYLE WS_EX_CONTROLPARENT -CAPTION "Add New Filter" -CLASS "TaigaFeedFilterW" -FONT 9, "Segoe UI", 400, 0, 0 -{ - LTEXT "Static", IDC_STATIC_HEADER, 10, 10, 410, 15, SS_LEFT | SS_NOPREFIX - PUSHBUTTON "< Back", IDNO, 265, 264, 50, 14, WS_DISABLED - DEFPUSHBUTTON "Next >", IDYES, 319, 264, 50, 14 - PUSHBUTTON "Cancel", IDCANCEL, 373, 264, 50, 14 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_FEED_FILTER_PAGE1 DIALOGEX 0, 0, 390, 210 -STYLE DS_3DLOOK | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - CONTROL "", IDC_LIST_FEED_FILTER_PRESETS, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 0, 0, 390, 210 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_FEED_FILTER_PAGE2 DIALOGEX 0, 0, 390, 210 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - LTEXT "Filter name", IDC_STATIC_HEADER1, 5, 5, 375, 8, SS_LEFT - LTEXT "", IDC_STATIC, 5, 15, 375, 1, NOT WS_GROUP | SS_LEFT | SS_SUNKEN, WS_EX_STATICEDGE - EDITTEXT IDC_EDIT_FEED_NAME, 10, 20, 360, 14, ES_AUTOHSCROLL - LTEXT "Conditions", IDC_STATIC_HEADER2, 5, 45, 375, 8, SS_LEFT - LTEXT "", IDC_STATIC, 5, 55, 375, 1, NOT WS_GROUP | SS_LEFT | SS_SUNKEN, WS_EX_STATICEDGE - CONTROL "", IDC_LIST_FEED_FILTER_CONDITIONS, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_NOSORTHEADER | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 10, 60, 343, 70 - CONTROL "", IDC_TOOLBAR_FEED_FILTER, "ToolbarWindow32", 0x560099CD, 355, 60, 15, 70 - LTEXT "Options", IDC_STATIC_HEADER3, 5, 137, 375, 8, SS_LEFT - LTEXT "", IDC_STATIC, 5, 147, 375, 1, NOT WS_GROUP | SS_LEFT | SS_SUNKEN, WS_EX_STATICEDGE - LTEXT "Match:", IDC_STATIC, 10, 152, 165, 8, SS_LEFT - COMBOBOX IDC_COMBO_FEED_FILTER_MATCH, 10, 162, 165, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS - LTEXT "Action:", IDC_STATIC, 205, 152, 165, 8, SS_LEFT - COMBOBOX IDC_COMBO_FEED_FILTER_ACTION, 205, 162, 165, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS - LTEXT "Discard type:", IDC_STATIC_FEED_FILTER_DISCARDTYPE, 205, 177, 165, 8, SS_LEFT - COMBOBOX IDC_COMBO_FEED_FILTER_OPTION, 205, 187, 165, 30, WS_TABSTOP | WS_DISABLED | CBS_DROPDOWNLIST | CBS_HASSTRINGS -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_FEED_FILTER_PAGE3 DIALOGEX 0, 0, 390, 210 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_VISIBLE | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - CONTROL "", IDC_LIST_FEED_FILTER_ANIME, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 0, 0, 390, 195 - LTEXT "Currently limited to: (nothing)", IDC_STATIC_FEED_FILTER_LIMIT, 0, 200, 390, 8, SS_LEFT | SS_NOPREFIX | SS_WORDELLIPSIS -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_FORMAT DIALOGEX 0, 0, 324, 137 -STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU -EXSTYLE WS_EX_CONTROLPARENT -CAPTION "Edit Format" -CLASS "TaigaFormatW" -FONT 9, "Segoe UI", 400, 0, 0 -{ - GROUPBOX "Format string", IDC_STATIC, 7, 7, 310, 102 - CONTROL "", IDC_RICHEDIT_FORMAT, RICHEDIT_CLASS, WS_TABSTOP | WS_VSCROLL | WS_CLIPCHILDREN | NOT WS_BORDER | ES_AUTOVSCROLL | ES_MULTILINE, 12, 18, 300, 55, WS_EX_STATICEDGE - LTEXT "Preview:", IDC_STATIC, 12, 80, 105, 8, SS_LEFT - EDITTEXT IDC_EDIT_PREVIEW, 12, 90, 300, 12, ES_AUTOHSCROLL | ES_READONLY - PUSHBUTTON "Add...", IDHELP, 7, 116, 50, 14 - DEFPUSHBUTTON "OK", IDOK, 213, 116, 50, 14 - PUSHBUTTON "Cancel", IDCANCEL, 267, 116, 50, 14 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_HISTORY DIALOG 0, 0, 444, 255 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW | WS_CLIPCHILDREN -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI" -{ - CONTROL "", IDC_LIST_EVENT, WC_LISTVIEW, WS_TABSTOP | LVS_ALIGNLEFT | LVS_NOSORTHEADER | LVS_SHAREIMAGELISTS | LVS_REPORT, 0, 0, 440, 250 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_INPUT DIALOG 0, 0, 250, 60 -STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU -EXSTYLE WS_EX_WINDOWEDGE | WS_EX_TOPMOST -CAPTION "Input" -FONT 9, "Segoe UI" -{ - LTEXT "Enter new value:", IDC_STATIC_INPUTINFO, 7, 7, 236, 8, SS_LEFT - EDITTEXT IDC_EDIT_INPUT, 7, 18, 236, 14, ES_AUTOHSCROLL - CONTROL "", IDC_SPIN_INPUT, UPDOWN_CLASS, UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_SETBUDDYINT, 232, 18, 11, 14 - DEFPUSHBUTTON "OK", IDOK, 139, 39, 50, 14 - PUSHBUTTON "Cancel", IDCANCEL, 193, 39, 50, 14 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_MAIN DIALOG 0, 0, 580, 400 -STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_CLIPCHILDREN | WS_GROUP | WS_TABSTOP | WS_THICKFRAME | WS_SYSMENU -EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT | WS_EX_APPWINDOW -CAPTION "Taiga" -CLASS "TaigaMainW" -FONT 9, "Segoe UI" -{ - CONTROL "", IDC_REBAR_MAIN, REBARCLASSNAME, WS_CLIPCHILDREN | WS_CLIPSIBLINGS | 0x00002641, 0, 0, 580, 18 - CONTROL "", IDC_TOOLBAR_MENU, "ToolbarWindow32", 0x56009845, 10, 0, 305, 16 - CONTROL "", IDC_TOOLBAR_MAIN, "ToolbarWindow32", 0x56009945, 15, 10, 305, 16 - CONTROL "", IDC_TOOLBAR_SEARCH, "ToolbarWindow32", 0x56008945, 430, 5, 135, 16 - EDITTEXT IDC_EDIT_SEARCH, 430, 30, 130, 11, WS_CLIPCHILDREN | ES_AUTOHSCROLL - PUSHBUTTON "", IDC_BUTTON_CANCELSEARCH, 555, 10, 10, 10, NOT WS_VISIBLE | BS_ICON - CONTROL "", IDC_TREE_MAIN, WC_TREEVIEW, WS_TABSTOP | TVS_SHOWSELALWAYS | TVS_TRACKSELECT | TVS_INFOTIP | TVS_FULLROWSELECT | TVS_NONEVENHEIGHT | TVS_NOHSCROLL, 5, 30, 115, 345 - CONTROL "", IDC_STATUSBAR_MAIN, "msctls_statusbar32", 0x50000900, 0, 387, 580, 13 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SEARCH DIALOGEX 0, 0, 400, 185 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW | WS_CLIPCHILDREN -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - CONTROL "", IDC_LIST_SEARCH, WC_LISTVIEW, WS_TABSTOP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | LVS_ALIGNLEFT | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 5, 5, 390, 174 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SEASON DIALOGEX 0, 0, 740, 425 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW | WS_CLIPCHILDREN -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - CONTROL "", IDC_REBAR_SEASON, REBARCLASSNAME, WS_CLIPCHILDREN | WS_CLIPSIBLINGS | 0x00000801, 0, 1, 740, 18 - CONTROL "", IDC_TOOLBAR_SEASON, "ToolbarWindow32", 0x5600994D, 15, 5, 510, 10 - CONTROL "", IDC_LIST_SEASON, WC_LISTVIEW, WS_TABSTOP | LVS_SINGLESEL | LVS_REPORT, 5, 30, 705, 370 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS DIALOG 0, 0, 430, 285 -STYLE DS_3DLOOK | DS_CENTER | DS_CONTEXTHELP | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_CLIPSIBLINGS | WS_POPUP | WS_SYSMENU -EXSTYLE WS_EX_CONTROLPARENT -CAPTION "Settings" -CLASS "TaigaSettingsW" -FONT 9, "Segoe UI" -{ - CONTROL "", IDC_TREE_SECTIONS, WC_TREEVIEW, WS_TABSTOP | WS_BORDER | TVS_HASBUTTONS | TVS_DISABLEDRAGDROP | TVS_SHOWSELALWAYS | TVS_TRACKSELECT | TVS_INFOTIP | TVS_FULLROWSELECT | TVS_NOSCROLL | TVS_NONEVENHEIGHT, 7, 7, 90, 250 - LTEXT "", IDC_STATIC_TITLE, 104, 7, 319, 12, SS_LEFT | SS_CENTERIMAGE | SS_NOPREFIX, WS_EX_STATICEDGE - CONTROL "", IDC_TAB_PAGES, WC_TABCONTROL, 0, 104, 26, 319, 231 - CONTROL "Restore default settings", IDC_LINK_DEFAULTS, "SysLink", 0x50010000, 7, 270, 90, 8 - DEFPUSHBUTTON "OK", IDOK, 319, 264, 50, 14 - PUSHBUTTON "Cancel", IDCANCEL, 373, 264, 50, 14 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_APP_BEHAVIOR DIALOG 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI" -{ - GROUPBOX "Startup", IDC_STATIC, 7, 7, 301, 64 - AUTOCHECKBOX "Start automatically with Windows", IDC_CHECK_AUTOSTART, 12, 18, 118, 10 - AUTOCHECKBOX "Start minimized", IDC_CHECK_START_MINIMIZE, 12, 30, 85, 10 - AUTOCHECKBOX "Check for updates for Taiga", IDC_CHECK_START_VERSION, 12, 42, 110, 10 - AUTOCHECKBOX "Scan available episodes", IDC_CHECK_START_CHECKEPS, 12, 54, 95, 10 - GROUPBOX "System tray use", IDC_STATIC, 7, 78, 301, 40 - AUTOCHECKBOX "Close to tray", IDC_CHECK_GENERAL_CLOSE, 12, 89, 70, 10 - AUTOCHECKBOX "Minimize to tray", IDC_CHECK_GENERAL_MINIMIZE, 12, 101, 85, 10 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_APP_CONNECTION DIALOGEX 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - GROUPBOX "Proxy", IDC_STATIC, 7, 7, 301, 69 - LTEXT "Host:", IDC_STATIC, 12, 18, 289, 8, SS_LEFT - EDITTEXT IDC_EDIT_PROXY_HOST, 12, 28, 289, 12, ES_AUTOHSCROLL - LTEXT "Username:", IDC_STATIC, 12, 47, 141, 8, SS_LEFT - EDITTEXT IDC_EDIT_PROXY_USER, 12, 57, 141, 12, ES_AUTOHSCROLL - LTEXT "Password:", IDC_STATIC, 160, 47, 141, 8, SS_LEFT - EDITTEXT IDC_EDIT_PROXY_PASS, 160, 57, 141, 12, ES_AUTOHSCROLL | ES_PASSWORD -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_APP_INTERFACE DIALOGEX 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - GROUPBOX "Theme", IDC_STATIC, 7, 7, 301, 56 - LTEXT "Current theme:", IDC_STATIC, 12, 18, 141, 8, SS_LEFT - COMBOBOX IDC_COMBO_THEME, 12, 28, 141, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS - CONTROL "Open theme folder", IDC_LINK_THEMES, "SysLink", 0x50010000, 160, 30, 90, 8 - LTEXT "Tip: With themes, you can change icons and progress bar colors.", IDC_STATIC, 12, 48, 289, 8, SS_LEFT | SS_NOPREFIX - GROUPBOX "External links", IDC_STATIC, 7, 70, 301, 82 - EDITTEXT IDC_EDIT_EXTERNALLINKS, 12, 81, 289, 64, WS_VSCROLL | ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_APP_LIST DIALOG 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI" -{ - GROUPBOX "Actions", IDC_STATIC, 7, 7, 301, 41 - LTEXT "Double click:", IDC_STATIC, 12, 18, 141, 8, SS_LEFT - COMBOBOX IDC_COMBO_DBLCLICK, 12, 28, 141, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS - LTEXT "Middle click:", IDC_STATIC, 160, 18, 141, 8, SS_LEFT - COMBOBOX IDC_COMBO_MDLCLICK, 160, 28, 141, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS - GROUPBOX "Appearance", IDC_STATIC, 7, 55, 301, 40 - AUTOCHECKBOX "Display English titles, if available", IDC_CHECK_LIST_ENGLISH, 12, 66, 125, 10 - AUTOCHECKBOX "Highlight new episodes", IDC_CHECK_HIGHLIGHT, 12, 78, 90, 10 - GROUPBOX "Progress bars", IDC_STATIC, 7, 102, 301, 40 - AUTOCHECKBOX "Display aired episodes (estimated)", IDC_CHECK_LIST_PROGRESS_AIRED, 12, 113, 200, 10 - AUTOCHECKBOX "Display available episodes on my computer", IDC_CHECK_LIST_PROGRESS_AVAILABLE, 12, 125, 170, 10 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_LIBRARY_CACHE DIALOGEX 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - GROUPBOX "Clear data", IDC_STATIC, 7, 7, 301, 52 - AUTOCHECKBOX "History", IDC_CHECK_CACHE1, 12, 18, 100, 10 - LTEXT "0 item(s)", IDC_STATIC_CACHE1, 119, 19, 100, 8, SS_LEFT - AUTOCHECKBOX "Image files", IDC_CHECK_CACHE2, 12, 30, 100, 10 - LTEXT "0 item(s), 0 KB", IDC_STATIC_CACHE2, 119, 31, 100, 8, SS_LEFT - AUTOCHECKBOX "Torrent files", IDC_CHECK_CACHE3, 12, 42, 100, 10 - LTEXT "0 item(s), 0 KB", IDC_STATIC_CACHE3, 119, 43, 100, 8, SS_LEFT - PUSHBUTTON "Clear", IDC_BUTTON_CACHE_CLEAR, 251, 38, 50, 14, WS_DISABLED -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_LIBRARY_FOLDERS DIALOGEX 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - GROUPBOX "Root folders", IDC_STATIC, 7, 7, 301, 101 - CONTROL "", IDC_LIST_FOLDERS_ROOT, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | LVS_SHAREIMAGELISTS | LVS_REPORT, 12, 18, 289, 65 - LTEXT "Tip: You can drag and drop folders here.", IDC_STATIC, 13, 90, 180, 8, SS_LEFT | SS_NOPREFIX - PUSHBUTTON "Add new...", IDC_BUTTON_ADDFOLDER, 197, 87, 50, 14 - PUSHBUTTON "Remove", IDC_BUTTON_REMOVEFOLDER, 251, 87, 50, 14, WS_DISABLED - GROUPBOX "Monitor", IDC_STATIC, 7, 115, 301, 28 - AUTOCHECKBOX "Watch folders for new files", IDC_CHECK_FOLDERS_WATCH, 12, 126, 130, 10 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_RECOGNITION_GENERAL DIALOG 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI" -{ - GROUPBOX "Validation", IDC_STATIC, 7, 7, 301, 28 - AUTOCHECKBOX "Ignore files outside of root folders", IDC_CHECK_UPDATE_ROOT, 12, 18, 130, 10 - GROUPBOX "List updates and automatic sharing", IDC_STATIC, 7, 42, 147, 54 - LTEXT "Delay time:", IDC_STATIC, 12, 53, 116, 8, SS_LEFT - EDITTEXT IDC_EDIT_DELAY, 12, 63, 40, 14, ES_AUTOHSCROLL | ES_NUMBER - CONTROL "", IDC_SPIN_DELAY, UPDOWN_CLASS, UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_NOTHOUSANDS | UDS_AUTOBUDDY | UDS_SETBUDDYINT, 42, 63, 12, 14 - LTEXT "(seconds)", IDC_STATIC, 56, 65, 45, 8, SS_LEFT - AUTOCHECKBOX "Media player must be in focus", IDC_CHECK_UPDATE_CHECKMP, 12, 79, 121, 10 - GROUPBOX "List updates", IDC_STATIC, 161, 42, 147, 54 - AUTOCHECKBOX "Episode must be in range", IDC_CHECK_UPDATE_RANGE, 167, 53, 130, 10 - AUTOCHECKBOX "Wait for media player to close", IDC_CHECK_UPDATE_WAITMP, 167, 65, 115, 10 - AUTOCHECKBOX "Ask for confirmation", IDC_CHECK_UPDATE_CONFIRM, 167, 77, 95, 10 - GROUPBOX "Behavior", IDC_STATIC, 7, 103, 301, 73 - AUTOCHECKBOX "Notify me when an episode is recognized", IDC_CHECK_NOTIFY_RECOGNIZED, 12, 114, 180, 10 - AUTOCHECKBOX "Notify me when an episode is not recognized", IDC_CHECK_NOTIFY_NOTRECOGNIZED, 12, 126, 180, 10 - PUSHBUTTON "Edit format string...", IDC_BUTTON_FORMAT_BALLOON, 12, 138, 85, 14 - AUTOCHECKBOX "Go to Now Playing when an episode is recognized", IDC_CHECK_UPDATE_GOTO, 12, 159, 180, 10 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_RECOGNITION_MEDIA DIALOG 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI" -{ - CONTROL "", IDC_LIST_MEDIA, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 7, 7, 301, 181, WS_EX_ACCEPTFILES - LTEXT "Tip: This is a list of supported media players and web browsers. You can disable the ones that you don't have on your system to improve performance.", IDC_STATIC, 7, 192, 301, 16, SS_LEFT -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_RECOGNITION_STREAM DIALOGEX 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - CONTROL "", IDC_LIST_STREAM_PROVIDER, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 7, 7, 301, 80 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_SERVICES_MAL DIALOG 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI" -{ - GROUPBOX "Account", IDC_STATIC, 7, 7, 301, 55 - LTEXT "Username:", IDC_STATIC, 12, 18, 141, 8, SS_LEFT - EDITTEXT IDC_EDIT_USER, 12, 28, 141, 12, ES_AUTOHSCROLL - LTEXT "Password:", IDC_STATIC, 160, 18, 141, 8, SS_LEFT - EDITTEXT IDC_EDIT_PASS, 160, 28, 141, 12, ES_AUTOHSCROLL | ES_PASSWORD - CONTROL "Create a new account", IDC_LINK_MAL, "SysLink", 0x50010000, 12, 47, 140, 8 - GROUPBOX "Synchronization", IDC_STATIC, 7, 69, 300, 29 - AUTOCHECKBOX "Synchronize automatically at startup", IDC_CHECK_START_LOGIN, 12, 80, 140, 10 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_SHARING_HTTP DIALOGEX 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - AUTOCHECKBOX "Send HTTP request", IDC_CHECK_HTTP, 7, 7, 80, 10 - GROUPBOX "Options", IDC_STATIC, 7, 24, 301, 61 - LTEXT "URL:", IDC_STATIC, 12, 35, 30, 8, SS_LEFT - EDITTEXT IDC_EDIT_HTTP_URL, 12, 45, 289, 12, ES_AUTOHSCROLL - PUSHBUTTON "Edit format string...", IDC_BUTTON_FORMAT_HTTP, 12, 64, 85, 14 - LTEXT "Tip: You can use this feature to update scripted forum signatures.", IDC_STATIC, 7, 92, 301, 8, SS_LEFT -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_SHARING_MESSENGER DIALOG 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI" -{ - AUTOCHECKBOX "Update personal message", IDC_CHECK_MESSENGER, 7, 7, 95, 10 - GROUPBOX "Options", IDC_STATIC, 7, 24, 301, 32 - PUSHBUTTON "Edit format string...", IDC_BUTTON_FORMAT_MSN, 12, 35, 85, 14 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_SHARING_MIRC DIALOGEX 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - AUTOCHECKBOX "Send message", IDC_CHECK_MIRC, 7, 7, 60, 10 - GROUPBOX "Options", IDC_STATIC, 7, 24, 301, 40 - PUSHBUTTON "Edit format string...", IDC_BUTTON_FORMAT_MIRC, 12, 35, 85, 14 - AUTOCHECKBOX "Send to all connected servers", IDC_CHECK_MIRC_MULTISERVER, 160, 35, 135, 10 - AUTOCHECKBOX "Use ""/me"" action", IDC_CHECK_MIRC_ACTION, 160, 47, 80, 10 - GROUPBOX "Service", IDC_STATIC, 7, 71, 301, 41 - LTEXT "DDE service name:", IDC_STATIC, 12, 82, 65, 8, SS_LEFT - EDITTEXT IDC_EDIT_MIRC_SERVICE, 12, 92, 215, 12, ES_AUTOHSCROLL - PUSHBUTTON "Test connection", IDC_BUTTON_MIRC_TEST, 231, 91, 70, 14 - GROUPBOX "Channels to send message", IDC_STATIC, 7, 119, 301, 66 - RADIOBUTTON "Active channel", IDC_RADIO_MIRC_CHANNEL1, 12, 130, 100, 10 - RADIOBUTTON "All open channels", IDC_RADIO_MIRC_CHANNEL2, 12, 142, 100, 10 - RADIOBUTTON "Custom: (separate by a comma)", IDC_RADIO_MIRC_CHANNEL3, 12, 154, 135, 10 - EDITTEXT IDC_EDIT_MIRC_CHANNELS, 22, 166, 205, 12, ES_AUTOHSCROLL -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_SHARING_SKYPE DIALOG 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI" -{ - AUTOCHECKBOX "Update mood message", IDC_CHECK_SKYPE, 7, 7, 100, 10 - GROUPBOX "Options", IDC_STATIC, 7, 24, 301, 32 - PUSHBUTTON "Edit format string...", IDC_BUTTON_FORMAT_SKYPE, 12, 35, 85, 14 - LTEXT "Tip: Don't forget to allow access to Taiga.exe from Skype window.", IDC_STATIC, 7, 63, 301, 8, SS_LEFT -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_SHARING_TWITTER DIALOG 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI" -{ - AUTOCHECKBOX "Post tweets to my Twitter account", IDC_CHECK_TWITTER, 7, 7, 125, 10 - GROUPBOX "Options", IDC_STATIC, 7, 24, 301, 32 - PUSHBUTTON "Edit format string...", IDC_BUTTON_FORMAT_TWITTER, 12, 35, 85, 14 - GROUPBOX "Authorization", IDC_STATIC, 7, 63, 301, 46 - CONTROL "Taiga is not authorized to post to your Twitter account yet.", IDC_LINK_TWITTER, "SysLink", 0x50010000, 12, 74, 275, 10 - PUSHBUTTON "Authorize...", IDC_BUTTON_TWITTER_AUTH, 12, 88, 85, 14 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_TORRENTS_DISCOVERY DIALOGEX 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - GROUPBOX "Sources", IDC_STATIC, 7, 7, 301, 71 - LTEXT "RSS feed for checking new releases:", IDC_STATIC, 12, 18, 289, 8, SS_LEFT - COMBOBOX IDC_COMBO_TORRENT_SOURCE, 12, 28, 289, 30, WS_TABSTOP | CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_HASSTRINGS - LTEXT "RSS feed for searching releases for a title:", IDC_STATIC, 12, 48, 289, 8, SS_LEFT - COMBOBOX IDC_COMBO_TORRENT_SEARCH, 12, 58, 289, 30, WS_TABSTOP | CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_HASSTRINGS - GROUPBOX "Automation", IDC_STATIC, 7, 85, 301, 50 - AUTOCHECKBOX "Check automatically", IDC_CHECK_TORRENT_AUTOCHECK, 12, 96, 100, 10 - EDITTEXT IDC_EDIT_TORRENT_INTERVAL, 22, 109, 45, 14, ES_AUTOHSCROLL | ES_NUMBER - CONTROL "", IDC_SPIN_TORRENT_INTERVAL, UPDOWN_CLASS, UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_NOTHOUSANDS | UDS_AUTOBUDDY | UDS_SETBUDDYINT, 57, 109, 12, 14 - LTEXT "(minutes)", IDC_STATIC, 71, 111, 30, 8, SS_LEFT - LTEXT "When there are new torrents:", IDC_STATIC, 160, 96, 125, 8, SS_LEFT - RADIOBUTTON "Notify me", IDC_RADIO_TORRENT_NEW1, 160, 106, 105, 10, WS_TABSTOP - RADIOBUTTON "Download automatically", IDC_RADIO_TORRENT_NEW2, 160, 118, 105, 10, WS_TABSTOP -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_TORRENTS_DOWNLOADS DIALOGEX 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - GROUPBOX "Application", IDC_STATIC, 7, 7, 301, 54 - RADIOBUTTON "Use the default application associated with .torrent files", IDC_RADIO_TORRENT_APP1, 12, 18, 210, 10, WS_TABSTOP - RADIOBUTTON "Use a custom application:", IDC_RADIO_TORRENT_APP2, 12, 30, 105, 10, WS_TABSTOP - EDITTEXT IDC_EDIT_TORRENT_APP, 21, 42, 260, 12, ES_AUTOHSCROLL - PUSHBUTTON "...", IDC_BUTTON_TORRENT_BROWSE_APP, 285, 42, 16, 12 - GROUPBOX "Location", IDC_STATIC, 7, 68, 301, 80 - AUTOCHECKBOX "Use anime folders as the download folder", IDC_CHECK_TORRENT_AUTOSETFOLDER, 12, 79, 160, 10 - AUTOCHECKBOX "If no anime folder is set, use this folder instead:", IDC_CHECK_TORRENT_AUTOUSEFOLDER, 21, 91, 201, 10 - COMBOBOX IDC_COMBO_TORRENT_FOLDER, 30, 103, 251, 101, WS_TABSTOP | CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_HASSTRINGS - PUSHBUTTON "...", IDC_BUTTON_TORRENT_BROWSE_FOLDER, 285, 103, 16, 12 - AUTOCHECKBOX "Create a subfolder using the anime title as its name", IDC_CHECK_TORRENT_AUTOCREATEFOLDER, 30, 118, 192, 8 - LTEXT "Tip: Currently this feature is only supported by uTorrent.", IDC_STATIC, 12, 133, 289, 8, SS_LEFT -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_SETTINGS_TORRENTS_FILTERS DIALOGEX 0, 0, 315, 215 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - AUTOCHECKBOX "Enable torrent filters", IDC_CHECK_TORRENT_FILTER, 7, 7, 95, 10 - LTEXT "Filters allow you to download the files you want and ignore the others.", IDC_STATIC, 7, 19, 301, 8, SS_LEFT - CONTROL "", IDC_LIST_TORRENT_FILTER, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 7, 29, 301, 161 - CONTROL "", IDC_TOOLBAR_FEED_FILTER, "ToolbarWindow32", 0x5600994D, 233, 192, 75, 15 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_STATS DIALOGEX 0, 0, 350, 277 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW | WS_CLIPCHILDREN -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 0 -{ - LTEXT "Anime list", IDC_STATIC_HEADER1, 7, 7, 300, 8, SS_LEFT - LTEXT "Anime count:\nEpisode count:\nLife spent watching:\nMean score:\nScore deviation:", IDC_STATIC, 19, 22, 70, 42, SS_LEFT - LTEXT "", IDC_STATIC_ANIME_STAT1, 96, 22, 215, 42, SS_LEFT | SS_NOPREFIX - LTEXT "Score distribution", IDC_STATIC_HEADER2, 7, 71, 300, 8, SS_LEFT - RTEXT "10\n9\n8\n7\n6\n5\n4\n3\n2\n1\n", IDC_STATIC, 16, 86, 10, 83, SS_RIGHT - CONTROL "", IDC_STATIC_ANIME_STAT2, WC_STATIC, SS_OWNERDRAW, 34, 86, 275, 83 - LTEXT "Local database", IDC_STATIC_HEADER3, 7, 176, 300, 8, SS_LEFT - LTEXT "Anime count:\nImage files:\nTorrent files:", IDC_STATIC, 19, 191, 70, 25, SS_LEFT - LTEXT "", IDC_STATIC_ANIME_STAT3, 96, 191, 215, 25, SS_LEFT | SS_NOPREFIX - LTEXT "Taiga", IDC_STATIC_HEADER4, 7, 223, 300, 8, SS_LEFT - LTEXT "Connections made:\nUptime:\nTigers harmed:", IDC_STATIC, 19, 238, 70, 25, SS_LEFT - LTEXT "", IDC_STATIC_ANIME_STAT4, 96, 238, 215, 25, SS_LEFT | SS_NOPREFIX -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_TEST_RECOGNITION DIALOGEX 0, 0, 600, 330 -STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_GROUP | WS_TABSTOP | WS_THICKFRAME | WS_SYSMENU -EXSTYLE WS_EX_WINDOWEDGE | WS_EX_CONTROLPARENT | WS_EX_APPWINDOW -CAPTION "Taiga Recognition Test" -FONT 9, "Segoe UI", 400, 0, 0 -{ - CONTROL "", IDC_LIST_TEST_RECOGNITION, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_REPORT, 0, 0, 600, 330 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_TORRENT DIALOGEX 0, 0, 580, 350 -STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW | WS_CLIPCHILDREN -EXSTYLE WS_EX_CONTROLPARENT -FONT 9, "Segoe UI", 400, 0, 162 -{ - CONTROL "", IDC_REBAR_TORRENT, REBARCLASSNAME, WS_CLIPCHILDREN | WS_CLIPSIBLINGS | 0x00000801, 0, 1, 580, 18 - CONTROL "", IDC_TOOLBAR_TORRENT, "ToolbarWindow32", 0x5600994D, 10, 5, 550, 10 - CONTROL "", IDC_LIST_TORRENT, WC_LISTVIEW, WS_TABSTOP | LVS_ALIGNLEFT | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 5, 25, 570, 315 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_UPDATE DIALOGEX 0, 0, 228, 40 -STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_SYSMENU -EXSTYLE WS_EX_APPWINDOW -CAPTION "Update" -CLASS "TaigaUpdateW" -FONT 9, "Segoe UI", 400, 0, 0 -{ - ICON IDI_MAIN, 0, 7, 7, 27, 26, SS_ICON | SS_NOTIFY | SS_REALSIZEIMAGE - LTEXT "Checking updates...", IDC_STATIC_UPDATE_PROGRESS, 41, 10, 180, 8, SS_LEFT | SS_WORDELLIPSIS - CONTROL "", IDC_PROGRESS_UPDATE, PROGRESS_CLASS, 0, 41, 20, 180, 8 -} - - - -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDD_UPDATE_NEW DIALOGEX 0, 0, 358, 210 -STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU -CAPTION "New Update" -FONT 9, "Segoe UI", 400, 0, 0 -{ - CONTROL "", IDC_RICHEDIT_UPDATE, RICHEDIT_CLASS, WS_VSCROLL | NOT WS_BORDER | NOT WS_TABSTOP | ES_AUTOVSCROLL | ES_MULTILINE | ES_READONLY, 41, 32, 310, 150, WS_EX_STATICEDGE - DEFPUSHBUTTON "Download", IDOK, 249, 189, 50, 14 - PUSHBUTTON "Skip", IDCANCEL, 301, 189, 50, 14 - ICON IDI_MAIN, 0, 7, 7, 27, 26, SS_ICON | SS_NOTIFY | SS_REALSIZEIMAGE - LTEXT "A new version of Taiga is available!", IDC_STATIC_UPDATE_TITLE, 41, 7, 310, 13, SS_LEFT | SS_WORDELLIPSIS - LTEXT "Release notes:", IDC_STATIC, 41, 22, 310, 8, SS_LEFT | SS_WORDELLIPSIS - LTEXT "Current version: 1.0.0", IDC_STATIC_UPDATE_DETAILS, 41, 192, 201, 8, SS_LEFT | SS_WORDELLIPSIS -} - - - -// -// String Table resources -// -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -STRINGTABLE -{ - IDS_KEYWORD_AUDIO "2CH, 5.1CH, 5.1, AAC, AC3, DTS, DTS5.1, DTS-ES, DUALAUDIO, DUAL AUDIO, FLAC, MP3, OGG, TRUEHD5.1, VORBIS" - IDS_KEYWORD_EXTENSION "MKV, AVI, MP4, OGM, RM, RMVB, WMV, DIVX, MOV, FLV, MPG, 3GP" - IDS_KEYWORD_EXTRA "ASS, BATCH, BD, BLURAY, BLU-RAY, COMPLETE, DIRECTOR'S CUT, DVD, DVD5, DVD9, DVD-R2J, DVDRIP, ENG, ENGLISH, HARDSUB, PS3, R2DVD, R2J, R2JDVD, RAW, REMASTERED, SOFTSUB, SUBBED, SUB, UNCENSORED, UNCUT, VOSTFR, WEBCAST, WIDESCREEN, WS" - IDS_KEYWORD_EXTRA_UNSAFE "END, FINAL, OAV, ONA, OVA" - IDS_KEYWORD_VERSION "V0, V2, V3, V4" - IDS_KEYWORD_VIDEO "8BIT, 10BIT, AVI, DIVX, H264, H.264, HD, HDTV, HI10P, HQ, LQ, RMVB, SD, TS, VFR, WMV, X264, X.264, XVID" - IDS_KEYWORD_EPISODE "EPISODE, EP., EP, VOLUME, VOL., VOL, EPS., EPS" - IDS_KEYWORD_EPISODE_PREFIX "EP., EP, E, VOL., VOL, EPS., \x7B2C" -} - - - -// -// Icon resources -// -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -IDI_MAIN ICON ".\\res\\Taiga.ico" +// Generated by ResEdit 1.6.2 +// Copyright (C) 2006-2014 +// http://www.resedit.net + +#include +#include +#include +#include "resource.h" + + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDR_MENU DATA "..\\..\\res\\menu.xml" + + + +// +// Dialog resources +// +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_ABOUT DIALOGEX 0, 0, 365, 219 +STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +CAPTION "About" +CLASS "TaigaAboutW" +FONT 9, "Segoe UI", 400, 0, 0 +{ + ICON IDI_MAIN, 0, 7, 7, 27, 26, SS_ICON | SS_NOTIFY | SS_REALSIZEIMAGE, WS_EX_LEFT + CONTROL "", IDC_RICHEDIT_ABOUT, RICHEDIT_CLASS, WS_VSCROLL | NOT WS_BORDER | NOT WS_TABSTOP | ES_AUTOVSCROLL | ES_MULTILINE | ES_READONLY, 48, 7, 310, 205, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_ANIME_INFO DIALOGEX 0, 0, 480, 310 +STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_SYSMENU +EXSTYLE WS_EX_WINDOWEDGE +CAPTION "Anime Information" +FONT 9, "Segoe UI", 400, 0, 0 +{ + CONTROL "", IDC_STATIC_ANIME_IMG, WC_STATIC, SS_OWNERDRAW | SS_NOTIFY, 7, 7, 95, 120, WS_EX_LEFT + EDITTEXT IDC_EDIT_ANIME_TITLE, 105, 4, 360, 15, NOT WS_BORDER | NOT WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY, WS_EX_LEFT + CONTROL "", IDC_TAB_ANIME, WC_TABCONTROL, 0, 106, 26, 360, 250, WS_EX_LEFT + CONTROL "", IDC_LINK_NOWPLAYING, "SysLink", 0x50010004, 7, 163, 95, 140, 0x00000000 + DEFPUSHBUTTON "OK", IDOK, 369, 289, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "Cancel", IDCANCEL, 423, 289, 50, 14, 0, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_ANIME_INFO_PAGE01 DIALOGEX 0, 0, 355, 240 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + LTEXT "Alternative titles", IDC_STATIC_HEADER1, 0, 0, 340, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_ANIME_ALT, 5, 15, 340, 12, NOT WS_BORDER | NOT WS_TABSTOP | ES_AUTOHSCROLL | ES_READONLY, WS_EX_LEFT + LTEXT "Details", IDC_STATIC_HEADER2, 0, 28, 340, 8, SS_LEFT, WS_EX_LEFT + LTEXT "Type:\nEpisodes:\nStatus:\nSeason:\nGenres:\nProducers:\nScore:", IDC_STATIC, 7, 43, 50, 60, SS_LEFT, WS_EX_LEFT + CONTROL "TV\n0\nFinished Airing\n2011 Winter\nAction, Adventure\nUnknown\n0.00", IDC_STATIC_ANIME_DETAILS, WC_STATIC, NOT WS_GROUP | SS_LEFTNOWORDWRAP | SS_NOPREFIX, 57, 43, 285, 60, WS_EX_LEFT + LTEXT "Synopsis", IDC_STATIC_HEADER3, 0, 103, 340, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_ANIME_SYNOPSIS, 5, 118, 340, 105, NOT WS_BORDER | NOT WS_TABSTOP | ES_AUTOVSCROLL | ES_MULTILINE | ES_READONLY, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_ANIME_INFO_PAGE02 DIALOGEX 0, 0, 355, 240 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + LTEXT "My list", IDC_STATIC_HEADER1, 0, 0, 340, 8, SS_LEFT, WS_EX_LEFT + LTEXT "Episodes watched:", IDC_STATIC, 7, 15, 100, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_ANIME_PROGRESS, 7, 25, 100, 14, ES_AUTOHSCROLL | ES_NUMBER, WS_EX_LEFT + CONTROL "", IDC_SPIN_PROGRESS, UPDOWN_CLASS, UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_AUTOBUDDY | UDS_SETBUDDYINT, 97, 25, 12, 14, WS_EX_LEFT + AUTOCHECKBOX "Re-watching", IDC_CHECK_ANIME_REWATCH, 122, 26, 75, 10, 0, WS_EX_LEFT + LTEXT "Status:", IDC_STATIC, 7, 45, 100, 8, SS_LEFT, WS_EX_LEFT + COMBOBOX IDC_COMBO_ANIME_STATUS, 7, 55, 100, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_LEFT + LTEXT "Score:", IDC_STATIC, 122, 45, 100, 8, SS_LEFT, WS_EX_LEFT + COMBOBOX IDC_COMBO_ANIME_SCORE, 122, 55, 100, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_LEFT + LTEXT "Tags:", IDC_STATIC, 7, 75, 60, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_ANIME_TAGS, 7, 85, 341, 14, ES_AUTOHSCROLL, WS_EX_LEFT + LTEXT "Start date:", IDC_STATIC, 7, 105, 90, 8, SS_LEFT, WS_EX_LEFT + CONTROL "", IDC_DATETIME_START, DATETIMEPICK_CLASS, WS_TABSTOP | DTS_SHOWNONE | DTS_RIGHTALIGN, 7, 115, 100, 14, WS_EX_LEFT + LTEXT "Finish date:", IDC_STATIC, 122, 105, 90, 8, SS_LEFT, WS_EX_LEFT + CONTROL "", IDC_DATETIME_FINISH, DATETIMEPICK_CLASS, WS_TABSTOP | DTS_SHOWNONE | DTS_RIGHTALIGN, 122, 115, 100, 14, WS_EX_LEFT + LTEXT "My settings", IDC_STATIC_HEADER2, 0, 136, 340, 8, SS_LEFT, WS_EX_LEFT + LTEXT "Alternative titles:", IDC_STATIC, 7, 151, 85, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_ANIME_ALT, 7, 161, 341, 14, ES_AUTOHSCROLL, WS_EX_LEFT + AUTOCHECKBOX "Use the first alternative title to search for torrents", IDC_CHECK_ANIME_ALT, 7, 179, 215, 8, 0, WS_EX_LEFT + LTEXT "Folder:", IDC_STATIC, 7, 194, 85, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_ANIME_FOLDER, 7, 204, 322, 14, ES_AUTOHSCROLL, WS_EX_LEFT + PUSHBUTTON "...", IDC_BUTTON_BROWSE, 333, 204, 15, 14, 0, WS_EX_LEFT + CONTROL "Fansub group preference: None (Change)", IDC_LINK_ANIME_FANSUB, "SysLink", 0x50010004, 7, 224, 335, 10, 0x00000000 +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_ANIME_LIST DIALOGEX 0, 0, 460, 355 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW | WS_CLIPCHILDREN +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + CONTROL "", IDC_TAB_MAIN, WC_TABCONTROL, WS_TABSTOP | WS_CLIPSIBLINGS, 5, 5, 450, 345, WS_EX_LEFT + CONTROL "", IDC_LIST_MAIN, WC_LISTVIEW, WS_TABSTOP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | LVS_ALIGNLEFT | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 5, 5, 448, 330, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_FEED_CONDITION DIALOGEX 0, 0, 360, 66 +STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU +CAPTION "Add Condition" +CLASS "TaigaFeedConditionW" +FONT 9, "Segoe UI", 400, 0, 0 +{ + LTEXT "Element:", IDC_STATIC, 7, 7, 120, 8, SS_LEFT, WS_EX_LEFT + COMBOBOX IDC_COMBO_FEED_ELEMENT, 7, 17, 120, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_LEFT + LTEXT "Operator:", IDC_STATIC, 134, 7, 92, 8, SS_LEFT, WS_EX_LEFT + COMBOBOX IDC_COMBO_FEED_OPERATOR, 134, 17, 92, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_LEFT + LTEXT "Value:", IDC_STATIC, 233, 7, 120, 8, SS_LEFT, WS_EX_LEFT + COMBOBOX IDC_COMBO_FEED_VALUE, 233, 17, 120, 30, WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWN | CBS_HASSTRINGS, WS_EX_LEFT + DEFPUSHBUTTON "OK", IDOK, 249, 45, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "Cancel", IDCANCEL, 303, 45, 50, 14, 0, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_FEED_FILTER DIALOGEX 0, 0, 430, 285 +STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +CAPTION "Add New Filter" +CLASS "TaigaFeedFilterW" +FONT 9, "Segoe UI", 400, 0, 0 +{ + LTEXT "", IDC_STATIC_HEADER, 10, 10, 410, 15, SS_LEFT | SS_NOPREFIX, WS_EX_LEFT + PUSHBUTTON "< Back", IDNO, 265, 264, 50, 14, WS_DISABLED, WS_EX_LEFT + DEFPUSHBUTTON "Next >", IDYES, 319, 264, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "Cancel", IDCANCEL, 373, 264, 50, 14, 0, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_FEED_FILTER_PAGE1 DIALOGEX 0, 0, 390, 210 +STYLE DS_3DLOOK | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + CONTROL "", IDC_LIST_FEED_FILTER_PRESETS, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 0, 0, 390, 210, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_FEED_FILTER_PAGE2 DIALOGEX 0, 0, 390, 210 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + LTEXT "Filter name", IDC_STATIC_HEADER1, 5, 5, 375, 8, SS_LEFT, WS_EX_LEFT + LTEXT "", IDC_STATIC, 5, 15, 375, 1, NOT WS_GROUP | SS_LEFT | SS_SUNKEN, WS_EX_STATICEDGE + EDITTEXT IDC_EDIT_FEED_NAME, 10, 20, 360, 14, ES_AUTOHSCROLL, WS_EX_LEFT + LTEXT "Conditions", IDC_STATIC_HEADER2, 5, 45, 375, 8, SS_LEFT, WS_EX_LEFT + LTEXT "", IDC_STATIC, 5, 55, 375, 1, NOT WS_GROUP | SS_LEFT | SS_SUNKEN, WS_EX_STATICEDGE + CONTROL "", IDC_LIST_FEED_FILTER_CONDITIONS, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_NOSORTHEADER | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 10, 60, 343, 70, WS_EX_LEFT + CONTROL "", IDC_TOOLBAR_FEED_FILTER, "ToolbarWindow32", 0x560099CD, 355, 60, 15, 70, 0x00000000 + LTEXT "Options", IDC_STATIC_HEADER3, 5, 137, 375, 8, SS_LEFT, WS_EX_LEFT + LTEXT "", IDC_STATIC, 5, 147, 375, 1, NOT WS_GROUP | SS_LEFT | SS_SUNKEN, WS_EX_STATICEDGE + LTEXT "Match:", IDC_STATIC, 10, 152, 165, 8, SS_LEFT, WS_EX_LEFT + COMBOBOX IDC_COMBO_FEED_FILTER_MATCH, 10, 162, 165, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_LEFT + LTEXT "Action:", IDC_STATIC, 205, 152, 165, 8, SS_LEFT, WS_EX_LEFT + COMBOBOX IDC_COMBO_FEED_FILTER_ACTION, 205, 162, 165, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_LEFT + LTEXT "Discard type:", IDC_STATIC_FEED_FILTER_DISCARDTYPE, 205, 177, 165, 8, SS_LEFT, WS_EX_LEFT + COMBOBOX IDC_COMBO_FEED_FILTER_OPTION, 205, 187, 165, 30, WS_TABSTOP | WS_DISABLED | CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_FEED_FILTER_PAGE3 DIALOGEX 0, 0, 390, 210 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_VISIBLE | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + CONTROL "", IDC_LIST_FEED_FILTER_ANIME, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 0, 0, 390, 195, WS_EX_LEFT + LTEXT "Currently limited to: (nothing)", IDC_STATIC_FEED_FILTER_LIMIT, 0, 200, 390, 8, SS_LEFT | SS_NOPREFIX | SS_WORDELLIPSIS, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_FORMAT DIALOGEX 0, 0, 324, 137 +STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +CAPTION "Edit Format" +CLASS "TaigaFormatW" +FONT 9, "Segoe UI", 400, 0, 0 +{ + GROUPBOX "Format string", IDC_STATIC, 7, 7, 310, 102, 0, WS_EX_LEFT + CONTROL "", IDC_RICHEDIT_FORMAT, RICHEDIT_CLASS, WS_TABSTOP | WS_VSCROLL | WS_CLIPCHILDREN | NOT WS_BORDER | ES_AUTOVSCROLL | ES_MULTILINE, 12, 18, 300, 55, WS_EX_STATICEDGE + LTEXT "Preview:", IDC_STATIC, 12, 80, 105, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_PREVIEW, 12, 90, 300, 12, ES_AUTOHSCROLL | ES_READONLY, WS_EX_LEFT + PUSHBUTTON "Add...", IDHELP, 7, 116, 50, 14, 0, WS_EX_LEFT + DEFPUSHBUTTON "OK", IDOK, 213, 116, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "Cancel", IDCANCEL, 267, 116, 50, 14, 0, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_HISTORY DIALOGEX 0, 0, 444, 255 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW | WS_CLIPCHILDREN +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + CONTROL "", IDC_LIST_EVENT, WC_LISTVIEW, WS_TABSTOP | LVS_ALIGNLEFT | LVS_NOSORTHEADER | LVS_SHAREIMAGELISTS | LVS_REPORT, 0, 0, 440, 250, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_INPUT DIALOGEX 0, 0, 250, 60 +STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU +EXSTYLE WS_EX_WINDOWEDGE | WS_EX_TOPMOST +CAPTION "Input" +FONT 9, "Segoe UI", 400, 0, 0 +{ + LTEXT "Enter new value:", IDC_STATIC_INPUTINFO, 7, 7, 236, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_INPUT, 7, 18, 236, 14, ES_AUTOHSCROLL, WS_EX_LEFT + CONTROL "", IDC_SPIN_INPUT, UPDOWN_CLASS, UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_SETBUDDYINT, 232, 18, 11, 14, WS_EX_LEFT + DEFPUSHBUTTON "OK", IDOK, 139, 39, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "Cancel", IDCANCEL, 193, 39, 50, 14, 0, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_MAIN DIALOGEX 0, 0, 580, 400 +STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_CLIPCHILDREN | WS_GROUP | WS_TABSTOP | WS_THICKFRAME | WS_SYSMENU +EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT | WS_EX_APPWINDOW +CAPTION "Taiga" +CLASS "TaigaMainW" +FONT 9, "Segoe UI", 400, 0, 0 +{ + CONTROL "", IDC_REBAR_MAIN, REBARCLASSNAME, WS_CLIPCHILDREN | WS_CLIPSIBLINGS | 0x00002641, 0, 0, 580, 18, WS_EX_LEFT + CONTROL "", IDC_TOOLBAR_MENU, "ToolbarWindow32", 0x56009845, 10, 0, 305, 16, 0x00000000 + CONTROL "", IDC_TOOLBAR_MAIN, "ToolbarWindow32", 0x56009945, 15, 10, 305, 16, 0x00000000 + CONTROL "", IDC_TOOLBAR_SEARCH, "ToolbarWindow32", 0x56008945, 430, 5, 135, 16, 0x00000000 + EDITTEXT IDC_EDIT_SEARCH, 430, 30, 130, 12, WS_CLIPCHILDREN | ES_AUTOHSCROLL, WS_EX_LEFT + PUSHBUTTON "", IDC_BUTTON_CANCELSEARCH, 555, 10, 10, 10, NOT WS_VISIBLE | BS_ICON, WS_EX_LEFT + CONTROL "", IDC_TREE_MAIN, WC_TREEVIEW, WS_TABSTOP | TVS_SHOWSELALWAYS | TVS_TRACKSELECT | TVS_INFOTIP | TVS_FULLROWSELECT | TVS_NONEVENHEIGHT | TVS_NOHSCROLL, 5, 30, 115, 345, WS_EX_LEFT + CONTROL "", IDC_STATUSBAR_MAIN, "msctls_statusbar32", 0x50000900, 0, 387, 580, 13, 0x00000000 +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SEARCH DIALOGEX 0, 0, 400, 185 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW | WS_CLIPCHILDREN +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + CONTROL "", IDC_LIST_SEARCH, WC_LISTVIEW, WS_TABSTOP | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | LVS_ALIGNLEFT | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 5, 5, 390, 174, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SEASON DIALOGEX 0, 0, 740, 425 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW | WS_CLIPCHILDREN +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + CONTROL "", IDC_REBAR_SEASON, REBARCLASSNAME, WS_CLIPCHILDREN | WS_CLIPSIBLINGS | 0x00000801, 0, 1, 740, 18, WS_EX_LEFT + CONTROL "", IDC_TOOLBAR_SEASON, "ToolbarWindow32", 0x5600994D, 15, 5, 510, 10, 0x00000000 + CONTROL "", IDC_LIST_SEASON, WC_LISTVIEW, WS_TABSTOP | LVS_SINGLESEL | LVS_REPORT, 5, 30, 705, 370, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS DIALOGEX 0, 0, 430, 285 +STYLE DS_3DLOOK | DS_CENTER | DS_CONTEXTHELP | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_CLIPSIBLINGS | WS_POPUP | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +CAPTION "Settings" +CLASS "TaigaSettingsW" +FONT 9, "Segoe UI", 400, 0, 0 +{ + CONTROL "", IDC_TREE_SECTIONS, WC_TREEVIEW, WS_TABSTOP | WS_BORDER | TVS_HASBUTTONS | TVS_DISABLEDRAGDROP | TVS_SHOWSELALWAYS | TVS_TRACKSELECT | TVS_INFOTIP | TVS_FULLROWSELECT | TVS_NOSCROLL | TVS_NONEVENHEIGHT, 7, 7, 90, 250, WS_EX_LEFT + LTEXT "", IDC_STATIC_TITLE, 104, 7, 319, 12, SS_LEFT | SS_CENTERIMAGE | SS_NOPREFIX, WS_EX_STATICEDGE + CONTROL "", IDC_TAB_PAGES, WC_TABCONTROL, 0, 104, 26, 319, 231, WS_EX_LEFT + CONTROL "Restore default settings", IDC_LINK_DEFAULTS, "SysLink", 0x50010004, 7, 270, 90, 8, 0x00000000 + DEFPUSHBUTTON "OK", IDOK, 319, 264, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "Cancel", IDCANCEL, 373, 264, 50, 14, 0, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_APP_BEHAVIOR DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + GROUPBOX "Startup", IDC_STATIC, 7, 7, 301, 64, 0, WS_EX_LEFT + AUTOCHECKBOX "Start automatically with Windows", IDC_CHECK_AUTOSTART, 12, 18, 118, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "Start minimized", IDC_CHECK_START_MINIMIZE, 12, 30, 85, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "Check for updates for Taiga", IDC_CHECK_START_VERSION, 12, 42, 110, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "Scan available episodes on my computer", IDC_CHECK_START_CHECKEPS, 12, 54, 155, 10, 0, WS_EX_LEFT + GROUPBOX "System tray use", IDC_STATIC, 7, 78, 301, 40, 0, WS_EX_LEFT + AUTOCHECKBOX "Close to tray", IDC_CHECK_GENERAL_CLOSE, 12, 89, 70, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "Minimize to tray", IDC_CHECK_GENERAL_MINIMIZE, 12, 101, 85, 10, 0, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_APP_CONNECTION DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + GROUPBOX "Proxy", IDC_STATIC, 7, 7, 301, 69, 0, WS_EX_LEFT + LTEXT "Host:", IDC_STATIC, 12, 18, 289, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_PROXY_HOST, 12, 28, 289, 12, ES_AUTOHSCROLL, WS_EX_LEFT + LTEXT "Username:", IDC_STATIC, 12, 47, 141, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_PROXY_USER, 12, 57, 141, 12, ES_AUTOHSCROLL, WS_EX_LEFT + LTEXT "Password:", IDC_STATIC, 160, 47, 141, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_PROXY_PASS, 160, 57, 141, 12, ES_AUTOHSCROLL | ES_PASSWORD, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_APP_INTERFACE DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + GROUPBOX "Theme", IDC_STATIC, 7, 7, 301, 56, 0, WS_EX_LEFT + LTEXT "Current theme:", IDC_STATIC, 12, 18, 141, 8, SS_LEFT, WS_EX_LEFT + COMBOBOX IDC_COMBO_THEME, 12, 28, 141, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_LEFT + CONTROL "Open theme folder", IDC_LINK_THEMES, "SysLink", 0x50010004, 160, 30, 90, 8, 0x00000000 + LTEXT "Tip: With themes, you can change icons and progress bar colors.", IDC_STATIC, 12, 48, 289, 8, SS_LEFT | SS_NOPREFIX, WS_EX_LEFT + GROUPBOX "External links", IDC_STATIC, 7, 70, 301, 82, 0, WS_EX_LEFT + EDITTEXT IDC_EDIT_EXTERNALLINKS, 12, 81, 289, 64, WS_VSCROLL | ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_APP_LIST DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + GROUPBOX "Actions", IDC_STATIC, 7, 7, 301, 41, 0, WS_EX_LEFT + LTEXT "Double click:", IDC_STATIC, 12, 18, 141, 8, SS_LEFT, WS_EX_LEFT + COMBOBOX IDC_COMBO_DBLCLICK, 12, 28, 141, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_LEFT + LTEXT "Middle click:", IDC_STATIC, 160, 18, 141, 8, SS_LEFT, WS_EX_LEFT + COMBOBOX IDC_COMBO_MDLCLICK, 160, 28, 141, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_LEFT + GROUPBOX "Appearance", IDC_STATIC, 7, 55, 301, 40, 0, WS_EX_LEFT + AUTOCHECKBOX "Display English titles, if available", IDC_CHECK_LIST_ENGLISH, 12, 66, 125, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "Highlight new episodes", IDC_CHECK_HIGHLIGHT, 12, 78, 90, 10, 0, WS_EX_LEFT + GROUPBOX "Progress bars", IDC_STATIC, 7, 102, 301, 40, 0, WS_EX_LEFT + AUTOCHECKBOX "Display aired episodes (estimated)", IDC_CHECK_LIST_PROGRESS_AIRED, 12, 113, 200, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "Display available episodes on my computer", IDC_CHECK_LIST_PROGRESS_AVAILABLE, 12, 125, 170, 10, 0, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_LIBRARY_CACHE DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + GROUPBOX "Clear data", IDC_STATIC, 7, 7, 301, 52, 0, WS_EX_LEFT + AUTOCHECKBOX "History", IDC_CHECK_CACHE1, 12, 18, 100, 10, 0, WS_EX_LEFT + LTEXT "0 item(s)", IDC_STATIC_CACHE1, 119, 19, 100, 8, SS_LEFT, WS_EX_LEFT + AUTOCHECKBOX "Image files", IDC_CHECK_CACHE2, 12, 30, 100, 10, 0, WS_EX_LEFT + LTEXT "0 item(s), 0 KB", IDC_STATIC_CACHE2, 119, 31, 100, 8, SS_LEFT, WS_EX_LEFT + AUTOCHECKBOX "Torrent files", IDC_CHECK_CACHE3, 12, 42, 100, 10, 0, WS_EX_LEFT + LTEXT "0 item(s), 0 KB", IDC_STATIC_CACHE3, 119, 43, 100, 8, SS_LEFT, WS_EX_LEFT + PUSHBUTTON "Clear", IDC_BUTTON_CACHE_CLEAR, 251, 38, 50, 14, WS_DISABLED, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_LIBRARY_FOLDERS DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_ACCEPTFILES | WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + GROUPBOX "Root folders", IDC_STATIC, 7, 7, 301, 101, 0, WS_EX_LEFT + CONTROL "", IDC_LIST_FOLDERS_ROOT, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | LVS_SHAREIMAGELISTS | LVS_REPORT, 12, 18, 289, 65, WS_EX_LEFT + LTEXT "Tip: You can drag and drop folders here.", IDC_STATIC, 13, 90, 180, 8, SS_LEFT | SS_NOPREFIX, WS_EX_LEFT + PUSHBUTTON "Add new...", IDC_BUTTON_ADDFOLDER, 197, 87, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "Remove", IDC_BUTTON_REMOVEFOLDER, 251, 87, 50, 14, WS_DISABLED, WS_EX_LEFT + GROUPBOX "Monitor", IDC_STATIC, 7, 115, 301, 28, 0, WS_EX_LEFT + AUTOCHECKBOX "Watch folders for new files", IDC_CHECK_FOLDERS_WATCH, 12, 126, 130, 10, 0, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_RECOGNITION_GENERAL DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + GROUPBOX "Validation", IDC_STATIC, 7, 7, 301, 28, 0, WS_EX_LEFT + AUTOCHECKBOX "Ignore files outside of root folders", IDC_CHECK_UPDATE_ROOT, 12, 18, 130, 10, 0, WS_EX_LEFT + GROUPBOX "List updates and automatic sharing", IDC_STATIC, 7, 42, 147, 54, 0, WS_EX_LEFT + LTEXT "Delay time:", IDC_STATIC, 12, 53, 116, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_DELAY, 12, 63, 40, 14, ES_AUTOHSCROLL | ES_NUMBER, WS_EX_LEFT + CONTROL "", IDC_SPIN_DELAY, UPDOWN_CLASS, UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_NOTHOUSANDS | UDS_AUTOBUDDY | UDS_SETBUDDYINT, 42, 63, 12, 14, WS_EX_LEFT + LTEXT "(seconds)", IDC_STATIC, 56, 65, 45, 8, SS_LEFT, WS_EX_LEFT + AUTOCHECKBOX "Media player must be in focus", IDC_CHECK_UPDATE_CHECKMP, 12, 79, 121, 10, 0, WS_EX_LEFT + GROUPBOX "List updates", IDC_STATIC, 161, 42, 147, 54, 0, WS_EX_LEFT + AUTOCHECKBOX "Episode must be in range", IDC_CHECK_UPDATE_RANGE, 167, 53, 130, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "Wait for media player to close", IDC_CHECK_UPDATE_WAITMP, 167, 65, 115, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "Ask for confirmation", IDC_CHECK_UPDATE_CONFIRM, 167, 77, 95, 10, 0, WS_EX_LEFT + GROUPBOX "Behavior", IDC_STATIC, 7, 103, 301, 73, 0, WS_EX_LEFT + AUTOCHECKBOX "Notify me when an episode is recognized", IDC_CHECK_NOTIFY_RECOGNIZED, 12, 114, 180, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "Notify me when an episode is not recognized", IDC_CHECK_NOTIFY_NOTRECOGNIZED, 12, 126, 180, 10, 0, WS_EX_LEFT + PUSHBUTTON "Edit format string...", IDC_BUTTON_FORMAT_BALLOON, 12, 138, 85, 14, 0, WS_EX_LEFT + AUTOCHECKBOX "Go to Now Playing when an episode is recognized", IDC_CHECK_UPDATE_GOTO, 12, 159, 180, 10, 0, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_RECOGNITION_MEDIA DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + CONTROL "", IDC_LIST_MEDIA, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 7, 7, 301, 181, WS_EX_ACCEPTFILES + LTEXT "Tip: This is a list of supported media players and web browsers. You can disable the ones that you don't have on your system to improve performance.", IDC_STATIC, 7, 192, 301, 16, SS_LEFT, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_RECOGNITION_STREAM DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + CONTROL "", IDC_LIST_STREAM_PROVIDER, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 7, 7, 301, 80, WS_EX_LEFT + LTEXT "Tip: Currently this feature only works with a web browser in English language setting.", IDC_STATIC, 7, 94, 301, 8, SS_LEFT, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_SERVICES_HUMMINGBIRD DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + GROUPBOX "Account", IDC_STATIC, 7, 7, 301, 55, 0, WS_EX_LEFT + LTEXT "Username:", IDC_STATIC, 12, 18, 141, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_USER_HUMMINGBIRD, 12, 28, 141, 12, ES_AUTOHSCROLL, WS_EX_LEFT + LTEXT "Password:", IDC_STATIC, 160, 18, 141, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_PASS_HUMMINGBIRD, 160, 28, 141, 12, ES_AUTOHSCROLL | ES_PASSWORD, WS_EX_LEFT + CONTROL "Create a new account", IDC_LINK_ACCOUNT_HUMMINGBIRD, "SysLink", 0x50010004, 12, 47, 140, 8, 0x00000000 +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_SERVICES_MAIN DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + GROUPBOX "Synchronization", IDC_STATIC, 7, 7, 300, 73, 0, WS_EX_LEFT + LTEXT "Active service:", IDC_STATIC, 12, 18, 138, 8, SS_LEFT, WS_EX_LEFT + COMBOBOX IDC_COMBO_SERVICE, 12, 28, 139, 30, WS_TABSTOP | CBS_DROPDOWNLIST | CBS_HASSTRINGS, WS_EX_LEFT + AUTOCHECKBOX "Synchronize automatically at startup", IDC_CHECK_START_LOGIN, 12, 48, 138, 10, 0, WS_EX_LEFT + LTEXT "Tip: Taiga is currently unable to synchronize multiple services at the same time.", IDC_STATIC, 12, 65, 288, 8, SS_LEFT, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_SERVICES_MAL DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + GROUPBOX "Account", IDC_STATIC, 7, 7, 301, 55, 0, WS_EX_LEFT + LTEXT "Username:", IDC_STATIC, 12, 18, 141, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_USER_MAL, 12, 28, 141, 12, ES_AUTOHSCROLL, WS_EX_LEFT + LTEXT "Password:", IDC_STATIC, 160, 18, 141, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_PASS_MAL, 160, 28, 141, 12, ES_AUTOHSCROLL | ES_PASSWORD, WS_EX_LEFT + CONTROL "Create a new account", IDC_LINK_ACCOUNT_MAL, "SysLink", 0x50010004, 12, 47, 140, 8, 0x00000000 +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_SHARING_HTTP DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + AUTOCHECKBOX "Send HTTP request", IDC_CHECK_HTTP, 7, 7, 80, 10, 0, WS_EX_LEFT + GROUPBOX "Options", IDC_STATIC, 7, 24, 301, 61, 0, WS_EX_LEFT + LTEXT "URL:", IDC_STATIC, 12, 35, 30, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_HTTP_URL, 12, 45, 289, 12, ES_AUTOHSCROLL, WS_EX_LEFT + PUSHBUTTON "Edit format string...", IDC_BUTTON_FORMAT_HTTP, 12, 64, 85, 14, 0, WS_EX_LEFT + LTEXT "Tip: You can use this feature to update scripted forum signatures.", IDC_STATIC, 7, 92, 301, 8, SS_LEFT, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_SHARING_MIRC DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + AUTOCHECKBOX "Send message", IDC_CHECK_MIRC, 7, 7, 60, 10, 0, WS_EX_LEFT + GROUPBOX "Options", IDC_STATIC, 7, 24, 301, 40, 0, WS_EX_LEFT + PUSHBUTTON "Edit format string...", IDC_BUTTON_FORMAT_MIRC, 12, 35, 85, 14, 0, WS_EX_LEFT + AUTOCHECKBOX "Send to all connected servers", IDC_CHECK_MIRC_MULTISERVER, 160, 35, 135, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "Use ""/me"" action", IDC_CHECK_MIRC_ACTION, 160, 47, 80, 10, 0, WS_EX_LEFT + GROUPBOX "Service", IDC_STATIC, 7, 71, 301, 41, 0, WS_EX_LEFT + LTEXT "DDE service name:", IDC_STATIC, 12, 82, 65, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_MIRC_SERVICE, 12, 92, 215, 12, ES_AUTOHSCROLL, WS_EX_LEFT + PUSHBUTTON "Test connection", IDC_BUTTON_MIRC_TEST, 231, 91, 70, 14, 0, WS_EX_LEFT + GROUPBOX "Channels to send message", IDC_STATIC, 7, 119, 301, 66, 0, WS_EX_LEFT + RADIOBUTTON "Active channel", IDC_RADIO_MIRC_CHANNEL1, 12, 130, 100, 10, 0, WS_EX_LEFT + RADIOBUTTON "All open channels", IDC_RADIO_MIRC_CHANNEL2, 12, 142, 100, 10, 0, WS_EX_LEFT + RADIOBUTTON "Custom: (separate by a comma)", IDC_RADIO_MIRC_CHANNEL3, 12, 154, 135, 10, 0, WS_EX_LEFT + EDITTEXT IDC_EDIT_MIRC_CHANNELS, 22, 166, 205, 12, ES_AUTOHSCROLL, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_SHARING_SKYPE DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + AUTOCHECKBOX "Update mood message", IDC_CHECK_SKYPE, 7, 7, 100, 10, 0, WS_EX_LEFT + GROUPBOX "Options", IDC_STATIC, 7, 24, 301, 32, 0, WS_EX_LEFT + PUSHBUTTON "Edit format string...", IDC_BUTTON_FORMAT_SKYPE, 12, 35, 85, 14, 0, WS_EX_LEFT + LTEXT "Tip: Don't forget to allow access to Taiga.exe from Skype window.", IDC_STATIC, 7, 63, 301, 8, SS_LEFT, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_SHARING_TWITTER DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + AUTOCHECKBOX "Post tweets to my Twitter account", IDC_CHECK_TWITTER, 7, 7, 125, 10, 0, WS_EX_LEFT + GROUPBOX "Options", IDC_STATIC, 7, 24, 301, 32, 0, WS_EX_LEFT + PUSHBUTTON "Edit format string...", IDC_BUTTON_FORMAT_TWITTER, 12, 35, 85, 14, 0, WS_EX_LEFT + GROUPBOX "Authorization", IDC_STATIC, 7, 63, 301, 46, 0, WS_EX_LEFT + CONTROL "Taiga is not authorized to post to your Twitter account yet.", IDC_LINK_TWITTER, "SysLink", 0x50010004, 12, 74, 275, 10, 0x00000000 + PUSHBUTTON "Authorize...", IDC_BUTTON_TWITTER_AUTH, 12, 88, 85, 14, 0, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_TORRENTS_DISCOVERY DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + GROUPBOX "Sources", IDC_STATIC, 7, 7, 301, 71, 0, WS_EX_LEFT + LTEXT "RSS feed for checking new releases:", IDC_STATIC, 12, 18, 289, 8, SS_LEFT, WS_EX_LEFT + COMBOBOX IDC_COMBO_TORRENT_SOURCE, 12, 28, 289, 30, WS_TABSTOP | CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_HASSTRINGS, WS_EX_LEFT + LTEXT "RSS feed for searching releases for a title:", IDC_STATIC, 12, 48, 289, 8, SS_LEFT, WS_EX_LEFT + COMBOBOX IDC_COMBO_TORRENT_SEARCH, 12, 58, 289, 30, WS_TABSTOP | CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_HASSTRINGS, WS_EX_LEFT + GROUPBOX "Automation", IDC_STATIC, 7, 85, 301, 94, 0, WS_EX_LEFT + AUTOCHECKBOX "Check new torrents automatically", IDC_CHECK_TORRENT_AUTOCHECK, 12, 96, 135, 10, 0, WS_EX_LEFT + LTEXT "Interval:", IDC_STATIC, 22, 109, 45, 8, SS_LEFT, WS_EX_LEFT + EDITTEXT IDC_EDIT_TORRENT_INTERVAL, 22, 119, 45, 14, ES_AUTOHSCROLL | ES_NUMBER, WS_EX_LEFT + CONTROL "", IDC_SPIN_TORRENT_INTERVAL, UPDOWN_CLASS, UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_NOTHOUSANDS | UDS_AUTOBUDDY | UDS_SETBUDDYINT, 57, 119, 12, 14, WS_EX_LEFT + LTEXT "(minutes)", IDC_STATIC, 71, 121, 30, 8, SS_LEFT, WS_EX_LEFT + LTEXT "When there are new torrents:", IDC_STATIC, 22, 140, 125, 8, SS_LEFT, WS_EX_LEFT + RADIOBUTTON "Notify me", IDC_RADIO_TORRENT_NEW1, 22, 150, 105, 10, WS_TABSTOP, WS_EX_LEFT + RADIOBUTTON "Download immediately", IDC_RADIO_TORRENT_NEW2, 22, 162, 105, 10, WS_TABSTOP, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_TORRENTS_DOWNLOADS DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + GROUPBOX "Application", IDC_STATIC, 7, 7, 301, 54, 0, WS_EX_LEFT + RADIOBUTTON "Use the default application associated with .torrent files", IDC_RADIO_TORRENT_APP1, 12, 18, 210, 10, WS_TABSTOP, WS_EX_LEFT + RADIOBUTTON "Use a custom application:", IDC_RADIO_TORRENT_APP2, 12, 30, 105, 10, WS_TABSTOP, WS_EX_LEFT + EDITTEXT IDC_EDIT_TORRENT_APP, 21, 42, 260, 12, ES_AUTOHSCROLL, WS_EX_LEFT + PUSHBUTTON "...", IDC_BUTTON_TORRENT_BROWSE_APP, 285, 42, 16, 12, 0, WS_EX_LEFT + GROUPBOX "Location", IDC_STATIC, 7, 68, 301, 80, 0, WS_EX_LEFT + AUTOCHECKBOX "Use anime folders as the download folder", IDC_CHECK_TORRENT_AUTOSETFOLDER, 12, 79, 160, 10, 0, WS_EX_LEFT + AUTOCHECKBOX "If no anime folder is set, use this folder instead:", IDC_CHECK_TORRENT_AUTOUSEFOLDER, 21, 91, 201, 10, 0, WS_EX_LEFT + COMBOBOX IDC_COMBO_TORRENT_FOLDER, 30, 103, 251, 101, WS_TABSTOP | CBS_DROPDOWN | CBS_AUTOHSCROLL | CBS_HASSTRINGS, WS_EX_LEFT + PUSHBUTTON "...", IDC_BUTTON_TORRENT_BROWSE_FOLDER, 285, 103, 16, 12, 0, WS_EX_LEFT + AUTOCHECKBOX "Create a subfolder using the anime title as its name", IDC_CHECK_TORRENT_AUTOCREATEFOLDER, 30, 118, 192, 8, 0, WS_EX_LEFT + LTEXT "Tip: Currently this feature is only supported by uTorrent.", IDC_STATIC, 12, 133, 289, 8, SS_LEFT, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_SETTINGS_TORRENTS_FILTERS DIALOGEX 0, 0, 315, 215 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + AUTOCHECKBOX "Enable torrent filters", IDC_CHECK_TORRENT_FILTER, 7, 7, 95, 10, 0, WS_EX_LEFT + LTEXT "Filters allow you to download the files you want and ignore the others.", IDC_STATIC, 7, 19, 301, 8, SS_LEFT, WS_EX_LEFT + CONTROL "", IDC_LIST_TORRENT_FILTER, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_NOCOLUMNHEADER | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 7, 29, 301, 161, WS_EX_LEFT + CONTROL "", IDC_TOOLBAR_FEED_FILTER, "ToolbarWindow32", 0x5600994D, 233, 192, 75, 15, 0x00000000 +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_STATS DIALOGEX 0, 0, 350, 277 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW | WS_CLIPCHILDREN +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 0 +{ + LTEXT "Anime list", IDC_STATIC_HEADER1, 7, 7, 300, 8, SS_LEFT, WS_EX_LEFT + LTEXT "Anime count:\nEpisode count:\nLife spent watching:\nMean score:\nScore deviation:", IDC_STATIC, 19, 22, 70, 42, SS_LEFT, WS_EX_LEFT + LTEXT "", IDC_STATIC_ANIME_STAT1, 96, 22, 215, 42, SS_LEFT | SS_NOPREFIX, WS_EX_LEFT + LTEXT "Score distribution", IDC_STATIC_HEADER2, 7, 71, 300, 8, SS_LEFT, WS_EX_LEFT + RTEXT "10\n9\n8\n7\n6\n5\n4\n3\n2\n1\n", IDC_STATIC, 16, 86, 10, 83, SS_RIGHT, WS_EX_LEFT + CONTROL "", IDC_STATIC_ANIME_STAT2, WC_STATIC, SS_OWNERDRAW, 34, 86, 275, 83, WS_EX_LEFT + LTEXT "Local database", IDC_STATIC_HEADER3, 7, 176, 300, 8, SS_LEFT, WS_EX_LEFT + LTEXT "Anime count:\nImage files:\nTorrent files:", IDC_STATIC, 19, 191, 70, 25, SS_LEFT, WS_EX_LEFT + LTEXT "", IDC_STATIC_ANIME_STAT3, 96, 191, 215, 25, SS_LEFT | SS_NOPREFIX, WS_EX_LEFT + LTEXT "Taiga", IDC_STATIC_HEADER4, 7, 223, 300, 8, SS_LEFT, WS_EX_LEFT + LTEXT "Connections made:\nUptime:\nTigers harmed:", IDC_STATIC, 19, 238, 70, 25, SS_LEFT, WS_EX_LEFT + LTEXT "", IDC_STATIC_ANIME_STAT4, 96, 238, 215, 25, SS_LEFT | SS_NOPREFIX, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_TEST_RECOGNITION DIALOGEX 0, 0, 600, 330 +STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_GROUP | WS_TABSTOP | WS_THICKFRAME | WS_SYSMENU +EXSTYLE WS_EX_WINDOWEDGE | WS_EX_CONTROLPARENT | WS_EX_APPWINDOW +CAPTION "Taiga Recognition Test" +FONT 9, "Segoe UI", 400, 0, 0 +{ + CONTROL "", IDC_LIST_TEST_RECOGNITION, WC_LISTVIEW, WS_TABSTOP | WS_BORDER | LVS_ALIGNLEFT | LVS_REPORT, 0, 0, 600, 330, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_TORRENT DIALOGEX 0, 0, 580, 350 +STYLE DS_3DLOOK | DS_CONTROL | DS_SHELLFONT | WS_CHILDWINDOW | WS_CLIPCHILDREN +EXSTYLE WS_EX_CONTROLPARENT +FONT 9, "Segoe UI", 400, 0, 162 +{ + CONTROL "", IDC_REBAR_TORRENT, REBARCLASSNAME, WS_CLIPCHILDREN | WS_CLIPSIBLINGS | 0x00000801, 0, 1, 580, 18, WS_EX_LEFT + CONTROL "", IDC_TOOLBAR_TORRENT, "ToolbarWindow32", 0x5600994D, 10, 5, 550, 10, 0x00000000 + CONTROL "", IDC_LIST_TORRENT, WC_LISTVIEW, WS_TABSTOP | LVS_ALIGNLEFT | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | LVS_REPORT, 5, 25, 570, 315, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_UPDATE DIALOGEX 0, 0, 228, 40 +STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "Update" +CLASS "TaigaUpdateW" +FONT 9, "Segoe UI", 400, 0, 0 +{ + ICON IDI_MAIN, 0, 7, 7, 27, 26, SS_ICON | SS_NOTIFY | SS_REALSIZEIMAGE, WS_EX_LEFT + LTEXT "Checking updates...", IDC_STATIC_UPDATE_PROGRESS, 41, 10, 180, 8, SS_LEFT | SS_WORDELLIPSIS, WS_EX_LEFT + CONTROL "", IDC_PROGRESS_UPDATE, PROGRESS_CLASS, 0, 41, 20, 180, 8, WS_EX_LEFT +} + + + +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDD_UPDATE_NEW DIALOGEX 0, 0, 358, 210 +STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU +CAPTION "New Update" +FONT 9, "Segoe UI", 400, 0, 0 +{ + CONTROL "", IDC_RICHEDIT_UPDATE, RICHEDIT_CLASS, WS_VSCROLL | NOT WS_BORDER | NOT WS_TABSTOP | ES_AUTOVSCROLL | ES_MULTILINE | ES_READONLY, 41, 32, 310, 150, WS_EX_STATICEDGE + DEFPUSHBUTTON "Download", IDOK, 249, 189, 50, 14, 0, WS_EX_LEFT + PUSHBUTTON "Skip", IDCANCEL, 301, 189, 50, 14, 0, WS_EX_LEFT + ICON IDI_MAIN, 0, 7, 7, 27, 26, SS_ICON | SS_NOTIFY | SS_REALSIZEIMAGE, WS_EX_LEFT + LTEXT "A new version of Taiga is available!", IDC_STATIC_UPDATE_TITLE, 41, 7, 310, 13, SS_LEFT | SS_WORDELLIPSIS, WS_EX_LEFT + LTEXT "Release notes:", IDC_STATIC, 41, 22, 310, 8, SS_LEFT | SS_WORDELLIPSIS, WS_EX_LEFT + LTEXT "Current version: 1.0.0", IDC_STATIC_UPDATE_DETAILS, 41, 192, 201, 8, SS_LEFT | SS_WORDELLIPSIS, WS_EX_LEFT +} + + + +// +// Icon resources +// +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +IDI_MAIN ICON "..\\..\\res\\Taiga.ico" diff --git a/script.cpp b/src/taiga/script.cpp similarity index 59% rename from script.cpp rename to src/taiga/script.cpp index d5ed4e31d..248e32355 100644 --- a/script.cpp +++ b/src/taiga/script.cpp @@ -1,484 +1,525 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "anime_db.h" -#include "anime_episode.h" -#include "common.h" -#include "myanimelist.h" -#include "settings.h" -#include "string.h" -#include "taiga.h" - -#include "dlg/dlg_format.h" - -// The idea behind Taiga's script functions is borrowed from Mp3tag, which -// itself got it from foobar2000. See the following links for more information: -// * http://wiki.hydrogenaudio.org/index.php?title=Foobar2000:Title_Formatting_Reference -// * http://help.mp3tag.de/main_scripting.html - -#define SCRIPT_FUNCTION_COUNT 21 -const wchar_t* script_functions[] = { - L"and", - L"cut", - L"equal", - L"gequal", - L"greater", - L"if", - L"if2", - L"ifequal", - L"lequal", - L"len", - L"less", - L"lower", - L"not", - L"num", - L"or", - L"pad", - L"replace", - L"substr", - L"triml", - L"trimr", - L"upper" -}; - -#define SCRIPT_VARIABLE_COUNT 22 -const wchar_t* script_variables[] = { - L"audio", - L"checksum", - L"episode", - L"extra", - L"file", - L"folder", - L"group", - L"id", - L"image", - L"manual", - L"name", - L"playstatus", - L"resolution", - L"rewatching", - L"score", - L"status", - L"title", - L"total", - L"user", - L"version", - L"video", - L"watched" -}; - -// ============================================================================= - -wstring EvaluateFunction(const wstring& func_name, const wstring& func_body) { - wstring str; - - // Parse parameters - vector body_parts; - size_t param_begin = 0, param_end = -1; - do { // Split by unescaped comma - do { - param_end = InStr(func_body, L",", param_end + 1); - } while (0 < param_end && param_end < func_body.length() - 1 && func_body[param_end - 1] == '\\'); - if (param_end == -1) param_end = func_body.length(); - - body_parts.push_back(func_body.substr(param_begin, param_end - param_begin)); - param_begin = param_end + 1; - } while (param_begin <= func_body.length()); - - // All functions should have parameters - if (body_parts.empty()) return L""; - - // $and(x,y) - // Returns true, if all arguments evaluate to true. - if (func_name == L"and") { - for (size_t i = 0; i < body_parts.size(); i++) { - if (body_parts[i].empty()) { - return L""; - } - } - return L"true"; - // $not(x) - // Returns true, if x is false. - } else if (func_name == L"not") { - if (body_parts.empty() || body_parts[0].empty()) { - return L"true"; - } - // $or(x,y) - // Returns true, if at least one argument evaluates to true. - } else if (func_name == L"or") { - for (size_t i = 0; i < body_parts.size(); i++) { - if (!body_parts[i].empty()) { - return L"true"; - } - } - - // $cut(string,len) - // Returns first len characters of string. - } else if (func_name == L"cut") { - if (body_parts.size() > 1) { - int length = ToInt(body_parts[1]); - if (length >= 0 && length < static_cast(body_parts[0].length())) { - body_parts[0].resize(length); - } - str = body_parts[0]; - } - - // $equal(x,y) - // Returns true, if x is equal to y. - } else if (func_name == L"equal") { - if (body_parts.size() > 1) { - if (IsNumeric(body_parts[0]) && IsNumeric(body_parts[1])) { - if (ToInt(body_parts[0]) == ToInt(body_parts[1])) return L"true"; - } else { - if (CompareStrings(body_parts[0], body_parts[1]) == 0) return L"true"; - } - } - // $gequal(x,y) - // Returns true, if x is greater as or equal to y. - } else if (func_name == L"gequal") { - if (body_parts.size() > 1) { - if (IsNumeric(body_parts[0]) && IsNumeric(body_parts[1])) { - if (ToInt(body_parts[0]) >= ToInt(body_parts[1])) return L"true"; - } else { - if (CompareStrings(body_parts[0], body_parts[1]) >= 0) return L"true"; - } - } - // $greater(x,y) - // Returns true, if x is greater than y. - } else if (func_name == L"greater") { - if (body_parts.size() > 1) { - if (IsNumeric(body_parts[0]) && IsNumeric(body_parts[1])) { - if (ToInt(body_parts[0]) > ToInt(body_parts[1])) return L"true"; - } else { - if (CompareStrings(body_parts[0], body_parts[1]) > 0) return L"true"; - } - } - // $lequal(x,y) - // Returns true, if x is less than or equal to y. - } else if (func_name == L"lequal") { - if (body_parts.size() > 1) { - if (IsNumeric(body_parts[0]) && IsNumeric(body_parts[1])) { - if (ToInt(body_parts[0]) <= ToInt(body_parts[1])) return L"true"; - } else { - if (CompareStrings(body_parts[0], body_parts[1]) <= 0) return L"true"; - } - } - // $less(x,y) - // Returns true, if x is less than y. - } else if (func_name == L"less") { - if (body_parts.size() > 1) { - if (IsNumeric(body_parts[0]) && IsNumeric(body_parts[1])) { - if (ToInt(body_parts[0]) < ToInt(body_parts[1])) return L"true"; - } else { - if (CompareStrings(body_parts[0], body_parts[1]) < 0) return L"true"; - } - } - - // $if() - } else if (func_name == L"if") { - switch (body_parts.size()) { - // $if(cond) - case 1: - str = body_parts[0]; - break; - // $if(cond,then) - case 2: - if (!body_parts[0].empty()) str = body_parts[1]; - break; - // $if(cond,then,else) - case 3: - str = !body_parts[0].empty() ? body_parts[1] : body_parts[2]; - break; - } - // $if2(a,else) - } else if (func_name == L"if2") { - if (body_parts.size() > 1) { - str = !body_parts[0].empty() ? body_parts[0] : body_parts[1]; - } - // $ifequal() - } else if (func_name == L"ifequal") { - switch (body_parts.size()) { - // $ifequal(n1,n2,then) - case 3: - if (body_parts[0] == body_parts[1]) str = body_parts[2]; - break; - // $ifequal(n1,n2,then,else) - case 4: - str = body_parts[0] == body_parts[1] ? body_parts[2] : body_parts[3]; - break; - } - - // $len(string) - // Returns length of string in characters. - } else if (func_name == L"len") { - str = ToWstr(static_cast(body_parts[0].length())); - - // $lower(string) - // Converts string to lowercase. - } else if (func_name == L"lower") { - str = ToLower_Copy(body_parts[0]); - // $upper(string) - // Converts string to uppercase. - } else if (func_name == L"upper") { - str = ToUpper_Copy(body_parts[0]); - - // $num(n,len) - // Formats the integer number n in decimal notation with len characters. - // Pads with zeros from the left if necessary. - } else if (func_name == L"num") { - if (body_parts.size() > 1) { - int length = ToInt(body_parts[1]); - if (length > static_cast(body_parts[0].length())) { - str.append(length - body_parts[0].length(), '0'); - } - } - str += body_parts[0]; - // $pad(s,len,chars) - // Pads string from the left with chars to len characters. - // If length of chars is smaller than len, padding will repeat. - } else if (func_name == L"pad") { - if (body_parts.size() == 2) body_parts.push_back(L" "); - if (body_parts.size() > 2) { - if (body_parts[2].empty()) body_parts[2] = L" "; - int length = ToInt(body_parts[1]); - if (length > static_cast(body_parts[0].length())) { - for (size_t i = 0; i < length - body_parts[0].length(); i++) { - str += body_parts[2].at(i % body_parts[2].length()); - } - } - } - str += body_parts[0]; - - // $replace(a,b,c) - // Replaces all occurrences of string b in string a with string c. - } else if (func_name == L"replace") { - if (body_parts.size() == 2) body_parts.push_back(L""); - if (body_parts.size() > 2) { - str = body_parts[0]; - Replace(str, body_parts[1], body_parts[2], true); - } - - // $substr(s,pos,n) - // Returns substring of string s, starting from pos with a length of n characters. - } else if (func_name == L"substr") { - if (body_parts.size() > 2) { - if (ToInt(body_parts[1]) <= static_cast(body_parts[0].length())) { - str = body_parts[0].substr(ToInt(body_parts[1]), ToInt(body_parts[2])); - } - } - - // $triml() - // Removes leading characters from string. - } else if (func_name == L"triml") { - // $triml(s,c) - if (body_parts.size() > 1) { - TrimLeft(body_parts[0], body_parts[1].c_str()); - // $triml(s) - } else { - TrimLeft(body_parts[0]); - } - // $trimr() - // Removes trailing characters from string. - } else if (func_name == L"trimr") { - // $trimr(s,c) - if (body_parts.size() > 1) { - TrimRight(body_parts[0], body_parts[1].c_str()); - // $trimr(s) - } else { - TrimRight(body_parts[0]); - } - } - - return str; -} - -// ============================================================================= - -bool IsScriptFunction(const wstring& str) { - for (int i = 0; i < SCRIPT_FUNCTION_COUNT; i++) { - if (str == script_functions[i]) return true; - } - return false; -} - -bool IsScriptVariable(const wstring& str) { - for (int i = 0; i < SCRIPT_VARIABLE_COUNT; i++) { - if (str == script_variables[i]) return true; - } - return false; -} - -// ============================================================================= - -wstring ReplaceVariables(wstring str, const anime::Episode& episode, bool url_encode, bool is_manual, bool is_preview) { - auto anime_item = AnimeDatabase.FindItem(episode.anime_id); - if (!anime_item && is_preview) anime_item = &PreviewAnime; - - #define VALIDATE(x, y) anime_item ? x : y - #define ENCODE(x) url_encode ? EscapeScriptEntities(EncodeUrl(x)) : EscapeScriptEntities(x) - #define REPLACE(x, y) \ - if (var == x) { \ - str.replace(pos_var, var.length() + 2, y); \ - pos_var += int(wstring(y).length()) - int(wstring(x).length()) + 1; \ - continue; \ - } - - // Prepare episode value - wstring episode_number = ToWstr(GetEpisodeHigh(episode.number)); - TrimLeft(episode_number, L"0"); - - // Replace variables - int pos_var = 0; - do { - pos_var = InStr(str, L"%", pos_var); - if (pos_var > -1) { - int pos_end = InStr(str, L"%", pos_var + 1); - if (pos_end > -1) { - wstring var = str.substr(pos_var + 1, pos_end - pos_var - 1); - if (IsScriptVariable(var)) { - REPLACE(L"title", VALIDATE(ENCODE(anime_item->GetTitle()), ENCODE(episode.title))); - REPLACE(L"watched", VALIDATE(ENCODE(mal::TranslateNumber(anime_item->GetMyLastWatchedEpisode(), L"")), L"")); - REPLACE(L"total", VALIDATE(ENCODE(mal::TranslateNumber(anime_item->GetEpisodeCount(), L"")), L"")); - REPLACE(L"score", VALIDATE(ENCODE(mal::TranslateNumber(anime_item->GetMyScore(), L"")), L"")); - REPLACE(L"id", VALIDATE(ENCODE(ToWstr(anime_item->GetId())), L"")); - REPLACE(L"image", VALIDATE(ENCODE(anime_item->GetImageUrl()), L"")); - REPLACE(L"status", VALIDATE(ENCODE(ToWstr(anime_item->GetMyStatus())), L"")); - REPLACE(L"rewatching", VALIDATE(ENCODE(ToWstr(anime_item->GetMyRewatching())), L"")); - REPLACE(L"name", ENCODE(episode.name)); - REPLACE(L"episode", ENCODE(episode_number)); - REPLACE(L"version", ENCODE(episode.version)); - REPLACE(L"group", ENCODE(episode.group)); - REPLACE(L"resolution", ENCODE(episode.resolution)); - REPLACE(L"video", ENCODE(episode.video_type)); - REPLACE(L"audio", ENCODE(episode.audio_type)); - REPLACE(L"checksum", ENCODE(episode.checksum)); - REPLACE(L"extra", ENCODE(episode.extras)); - REPLACE(L"file", ENCODE(episode.file)); - REPLACE(L"folder", ENCODE(episode.folder)); - REPLACE(L"user", ENCODE(Settings.Account.MAL.user)); - REPLACE(L"manual", is_manual ? L"true" : L""); - switch (Taiga.play_status) { - case PLAYSTATUS_STOPPED: REPLACE(L"playstatus", L"stopped"); break; - case PLAYSTATUS_PLAYING: REPLACE(L"playstatus", L"playing"); break; - case PLAYSTATUS_UPDATED: REPLACE(L"playstatus", L"updated"); break; - } - } else { - pos_var = pos_end + 1; - } - } else { - pos_var++; - } - } - } while (pos_var > -1); - - #undef REPLACE - #undef ENCODE - #undef VALIDATE - - // Replace special characters - Replace(str, L"\\n", L"\n", true); - Replace(str, L"\\t", L"\t", true); - - // Scripting - int pos_func = 0, pos_left = 0, pos_right = 0; - int open_brackets = 0; - do { - // Find non-escaped dollar sign - pos_func = 0; - do { - pos_func = InStr(str, L"$", pos_func); - } while (0 < pos_func && pos_func < str.length() && str[pos_func - 1] == '\\'); - - if (pos_func > -1) { - for (unsigned int i = pos_func; i < str.length(); i++) { - switch (str[i]) { - case '$': - pos_func = i; - pos_left = pos_right = open_brackets = 0; - break; - case '(': - if (pos_func > -1) { - if (!open_brackets++) pos_left = i; - pos_right = 0; - } - break; - case ')': - if (pos_left) { - if (open_brackets == 1) { - pos_right = i; - wstring func_name = str.substr(pos_func + 1, pos_left - (pos_func + 1)); - wstring func_body = str.substr(pos_left + 1, pos_right - (pos_left + 1)); - str = str.substr(0, pos_func) + str.substr(pos_right + 1, str.length() - (pos_right + 1)); - str.insert(pos_func, EvaluateFunction(func_name, func_body)); - i = str.length(); - } - if (open_brackets > 0) open_brackets--; - } - break; - case '\\': - i++; - break; - } - } - if (!pos_left || !pos_right) break; - } - } while (pos_func > -1); - - - // Unescape - str = UnescapeScriptEntities(str); - - // Clean-up - Replace(str, L"\n\n", L"\n", true); - Replace(str, L" ", L" ", true); - - // Return - return str; -} - -wstring EscapeScriptEntities(const wstring& str) { - wstring escaped; - size_t entity_pos; - for (size_t pos = 0; pos <= str.length(); ) { - entity_pos = InStrChars(str, L"$,()%\\", pos); - if (entity_pos != -1) { - escaped.append(str, pos, entity_pos - pos); - escaped.append(L"\\"); - escaped.append(str, entity_pos, 1); - } else { - entity_pos = str.length(); - escaped.append(str, pos, entity_pos - pos); - } - pos = entity_pos + 1; - } - return escaped; -} - -wstring UnescapeScriptEntities(const wstring& str) { - wstring unescaped; - size_t entity_pos; - for (size_t pos = 0; pos <= str.length(); ) { - entity_pos = InStr(str, L"\\", pos); - if (entity_pos == -1) entity_pos = str.length(); - unescaped.append(str, pos, entity_pos - pos); - pos = entity_pos + 1; - } - return unescaped; +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/string.h" +#include "base/url.h" +#include "library/anime_db.h" +#include "library/anime_episode.h" +#include "library/anime_util.h" +#include "sync/hummingbird_util.h" +#include "sync/myanimelist_util.h" +#include "sync/sync.h" +#include "taiga/dummy.h" +#include "taiga/script.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" +#include "ui/ui.h" + +// The idea behind Taiga's script functions is borrowed from Mp3tag, which +// itself got it from foobar2000. See the following links for more information: +// http://wiki.hydrogenaudio.org/index.php?title=Foobar2000:Title_Formatting_Reference +// http://help.mp3tag.de/main_scripting.html + +#define SCRIPT_FUNCTION_COUNT 21 +const wchar_t* script_functions[] = { + L"and", + L"cut", + L"equal", + L"gequal", + L"greater", + L"if", + L"if2", + L"ifequal", + L"lequal", + L"len", + L"less", + L"lower", + L"not", + L"num", + L"or", + L"pad", + L"replace", + L"substr", + L"triml", + L"trimr", + L"upper" +}; + +#define SCRIPT_VARIABLE_COUNT 23 +const wchar_t* script_variables[] = { + L"animeurl", + L"audio", + L"checksum", + L"episode", + L"extra", + L"file", + L"folder", + L"group", + L"id", + L"image", + L"manual", + L"name", + L"playstatus", + L"resolution", + L"rewatching", + L"score", + L"status", + L"title", + L"total", + L"user", + L"version", + L"video", + L"watched" +}; + +//////////////////////////////////////////////////////////////////////////////// + +std::wstring EvaluateFunction(const std::wstring& func_name, + const std::wstring& func_body) { + std::wstring str; + + // Parse parameters + std::vector body_parts; + size_t param_begin = 0, param_end = -1; + do { // Split by unescaped comma + do { + param_end = InStr(func_body, L",", param_end + 1); + } while (0 < param_end && + param_end < func_body.length() - 1 && + func_body[param_end - 1] == '\\'); + if (param_end == -1) + param_end = func_body.length(); + + body_parts.push_back( + func_body.substr(param_begin, param_end - param_begin)); + param_begin = param_end + 1; + } while (param_begin <= func_body.length()); + + // All functions should have parameters + if (body_parts.empty()) + return std::wstring(); + + // $and(x,y) + // Returns true, if all arguments evaluate to true. + if (func_name == L"and") { + for (size_t i = 0; i < body_parts.size(); i++) + if (body_parts[i].empty()) + return std::wstring(); + return L"true"; + // $not(x) + // Returns true, if x is false. + } else if (func_name == L"not") { + if (body_parts.empty() || body_parts[0].empty()) + return L"true"; + // $or(x,y) + // Returns true, if at least one argument evaluates to true. + } else if (func_name == L"or") { + for (size_t i = 0; i < body_parts.size(); i++) + if (!body_parts[i].empty()) + return L"true"; + + // $cut(string,len) + // Returns first len characters of string. + } else if (func_name == L"cut") { + if (body_parts.size() > 1) { + int length = ToInt(body_parts[1]); + if (length >= 0 && length < static_cast(body_parts[0].length())) + body_parts[0].resize(length); + str = body_parts[0]; + } + + // $equal(x,y) + // Returns true, if x is equal to y. + } else if (func_name == L"equal") { + if (body_parts.size() > 1) { + if (IsNumeric(body_parts[0]) && IsNumeric(body_parts[1])) { + if (ToInt(body_parts[0]) == ToInt(body_parts[1])) + return L"true"; + } else { + if (CompareStrings(body_parts[0], body_parts[1]) == 0) + return L"true"; + } + } + // $gequal(x,y) + // Returns true, if x is greater as or equal to y. + } else if (func_name == L"gequal") { + if (body_parts.size() > 1) { + if (IsNumeric(body_parts[0]) && IsNumeric(body_parts[1])) { + if (ToInt(body_parts[0]) >= ToInt(body_parts[1])) + return L"true"; + } else { + if (CompareStrings(body_parts[0], body_parts[1]) >= 0) + return L"true"; + } + } + // $greater(x,y) + // Returns true, if x is greater than y. + } else if (func_name == L"greater") { + if (body_parts.size() > 1) { + if (IsNumeric(body_parts[0]) && IsNumeric(body_parts[1])) { + if (ToInt(body_parts[0]) > ToInt(body_parts[1])) + return L"true"; + } else { + if (CompareStrings(body_parts[0], body_parts[1]) > 0) + return L"true"; + } + } + // $lequal(x,y) + // Returns true, if x is less than or equal to y. + } else if (func_name == L"lequal") { + if (body_parts.size() > 1) { + if (IsNumeric(body_parts[0]) && IsNumeric(body_parts[1])) { + if (ToInt(body_parts[0]) <= ToInt(body_parts[1])) + return L"true"; + } else { + if (CompareStrings(body_parts[0], body_parts[1]) <= 0) + return L"true"; + } + } + // $less(x,y) + // Returns true, if x is less than y. + } else if (func_name == L"less") { + if (body_parts.size() > 1) { + if (IsNumeric(body_parts[0]) && IsNumeric(body_parts[1])) { + if (ToInt(body_parts[0]) < ToInt(body_parts[1])) + return L"true"; + } else { + if (CompareStrings(body_parts[0], body_parts[1]) < 0) + return L"true"; + } + } + + // $if() + } else if (func_name == L"if") { + switch (body_parts.size()) { + // $if(cond) + case 1: + str = body_parts[0]; + break; + // $if(cond,then) + case 2: + if (!body_parts[0].empty()) + str = body_parts[1]; + break; + // $if(cond,then,else) + case 3: + str = !body_parts[0].empty() ? body_parts[1] : body_parts[2]; + break; + } + // $if2(a,else) + } else if (func_name == L"if2") { + if (body_parts.size() > 1) + str = !body_parts[0].empty() ? body_parts[0] : body_parts[1]; + // $ifequal() + } else if (func_name == L"ifequal") { + switch (body_parts.size()) { + // $ifequal(n1,n2,then) + case 3: + if (body_parts[0] == body_parts[1]) + str = body_parts[2]; + break; + // $ifequal(n1,n2,then,else) + case 4: + str = body_parts[0] == body_parts[1] ? body_parts[2] : body_parts[3]; + break; + } + + // $len(string) + // Returns length of string in characters. + } else if (func_name == L"len") { + str = ToWstr(static_cast(body_parts[0].length())); + + // $lower(string) + // Converts string to lowercase. + } else if (func_name == L"lower") { + str = ToLower_Copy(body_parts[0]); + // $upper(string) + // Converts string to uppercase. + } else if (func_name == L"upper") { + str = ToUpper_Copy(body_parts[0]); + + // $num(n,len) + // Formats the integer number n in decimal notation with len characters. + // Pads with zeros from the left if necessary. + } else if (func_name == L"num") { + if (body_parts.size() > 1) { + int length = ToInt(body_parts[1]); + if (length > static_cast(body_parts[0].length())) + str.append(length - body_parts[0].length(), '0'); + } + str += body_parts[0]; + // $pad(s,len,chars) + // Pads string from the left with chars to len characters. + // If length of chars is smaller than len, padding will repeat. + } else if (func_name == L"pad") { + if (body_parts.size() == 2) + body_parts.push_back(L" "); + if (body_parts.size() > 2) { + if (body_parts[2].empty()) + body_parts[2] = L" "; + int length = ToInt(body_parts[1]); + if (length > static_cast(body_parts[0].length())) + for (size_t i = 0; i < length - body_parts[0].length(); i++) + str += body_parts[2].at(i % body_parts[2].length()); + } + str += body_parts[0]; + + // $replace(a,b,c) + // Replaces all occurrences of string b in string a with string c. + } else if (func_name == L"replace") { + if (body_parts.size() == 2) body_parts.push_back(L""); + if (body_parts.size() > 2) { + str = body_parts[0]; + Replace(str, body_parts[1], body_parts[2], true); + } + + // $substr(s,pos,n) + // Returns substring of string s, starting from pos with a length of n characters. + } else if (func_name == L"substr") { + if (body_parts.size() > 2) + if (ToInt(body_parts[1]) <= static_cast(body_parts[0].length())) + str = body_parts[0].substr(ToInt(body_parts[1]), ToInt(body_parts[2])); + + // $triml() + // Removes leading characters from string. + } else if (func_name == L"triml") { + // $triml(s,c) + if (body_parts.size() > 1) { + TrimLeft(body_parts[0], body_parts[1].c_str()); + // $triml(s) + } else { + TrimLeft(body_parts[0]); + } + // $trimr() + // Removes trailing characters from string. + } else if (func_name == L"trimr") { + // $trimr(s,c) + if (body_parts.size() > 1) { + TrimRight(body_parts[0], body_parts[1].c_str()); + // $trimr(s) + } else { + TrimRight(body_parts[0]); + } + } + + return str; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool IsScriptFunction(const std::wstring& str) { + for (int i = 0; i < SCRIPT_FUNCTION_COUNT; i++) + if (str == script_functions[i]) + return true; + + return false; +} + +bool IsScriptVariable(const std::wstring& str) { + for (int i = 0; i < SCRIPT_VARIABLE_COUNT; i++) + if (str == script_variables[i]) + return true; + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + +std::wstring ReplaceVariables(std::wstring str, const anime::Episode& episode, + bool url_encode, bool is_manual, bool is_preview) { + auto anime_item = AnimeDatabase.FindItem(episode.anime_id); + if (!anime_item && is_preview) + anime_item = &taiga::DummyAnime; + + std::wstring id; + if (anime_item) + id = anime_item->GetId(taiga::GetCurrentServiceId()); + + #define VALIDATE(x, y) \ + anime_item ? x : y + #define ENCODE(x) \ + url_encode ? EscapeScriptEntities(EncodeUrl(x)) : EscapeScriptEntities(x) + #define REPLACE(x, y) \ + if (var == x) { \ + str.replace(pos_var, var.length() + 2, y); \ + pos_var += static_cast(std::wstring(y).length()); \ + continue; \ + } + + // Prepare episode value + std::wstring episode_number = ToWstr(anime::GetEpisodeHigh(episode.number)); + TrimLeft(episode_number, L"0"); + + // Replace variables + int pos_var = 0; + do { + pos_var = InStr(str, L"%", pos_var); + if (pos_var > -1) { + int pos_end = InStr(str, L"%", pos_var + 1); + if (pos_end > -1) { + std::wstring var = str.substr(pos_var + 1, pos_end - pos_var - 1); + if (IsScriptVariable(var)) { + REPLACE(L"title", VALIDATE(ENCODE(anime_item->GetTitle()), ENCODE(episode.title))); + REPLACE(L"watched", VALIDATE(ENCODE(anime::TranslateNumber(anime_item->GetMyLastWatchedEpisode(), L"")), L"")); + REPLACE(L"total", VALIDATE(ENCODE(anime::TranslateNumber(anime_item->GetEpisodeCount(), L"")), L"")); + REPLACE(L"score", VALIDATE(ENCODE(anime::TranslateNumber(anime_item->GetMyScore(), L"")), L"")); + REPLACE(L"id", ENCODE(id)); + REPLACE(L"image", VALIDATE(ENCODE(anime_item->GetImageUrl()), L"")); + REPLACE(L"status", VALIDATE(ENCODE(ToWstr(anime_item->GetMyStatus())), L"")); + REPLACE(L"rewatching", VALIDATE(ENCODE(ToWstr(anime_item->GetMyRewatching())), L"")); + REPLACE(L"name", ENCODE(episode.name)); + REPLACE(L"episode", ENCODE(episode_number)); + REPLACE(L"version", ENCODE(episode.version)); + REPLACE(L"group", ENCODE(episode.group)); + REPLACE(L"resolution", ENCODE(episode.resolution)); + REPLACE(L"video", ENCODE(episode.video_type)); + REPLACE(L"audio", ENCODE(episode.audio_type)); + REPLACE(L"checksum", ENCODE(episode.checksum)); + REPLACE(L"extra", ENCODE(episode.extras)); + REPLACE(L"file", ENCODE(episode.file)); + REPLACE(L"folder", ENCODE(episode.folder)); + REPLACE(L"user", ENCODE(taiga::GetCurrentUsername())); + REPLACE(L"manual", is_manual ? L"true" : L""); + switch (Taiga.play_status) { + case taiga::kPlayStatusStopped: REPLACE(L"playstatus", L"stopped"); break; + case taiga::kPlayStatusPlaying: REPLACE(L"playstatus", L"playing"); break; + case taiga::kPlayStatusUpdated: REPLACE(L"playstatus", L"updated"); break; + } + switch (taiga::GetCurrentServiceId()) { + case sync::kMyAnimeList: + REPLACE(L"animeurl", ENCODE(sync::myanimelist::GetAnimePage(*anime_item))); + break; + case sync::kHummingbird: + REPLACE(L"animeurl", ENCODE(sync::hummingbird::GetAnimePage(*anime_item))); + break; + } + } else { + pos_var = pos_end + 1; + } + } else { + pos_var++; + } + } + } while (pos_var > -1); + + #undef REPLACE + #undef ENCODE + #undef VALIDATE + + // Replace special characters + Replace(str, L"\\n", L"\n", true); + Replace(str, L"\\t", L"\t", true); + + // Scripting + int pos_func = 0, pos_left = 0, pos_right = 0; + int open_brackets = 0; + do { + // Find non-escaped dollar sign + pos_func = 0; + do { + pos_func = InStr(str, L"$", pos_func); + } while (0 < pos_func && + pos_func < str.length() && + str[pos_func - 1] == '\\'); + + if (pos_func > -1) { + for (unsigned int i = pos_func; i < str.length(); i++) { + switch (str[i]) { + case '$': + pos_func = i; + pos_left = pos_right = open_brackets = 0; + break; + case '(': + if (pos_func > -1) { + if (!open_brackets++) + pos_left = i; + pos_right = 0; + } + break; + case ')': + if (pos_left) { + if (open_brackets == 1) { + pos_right = i; + std::wstring func_name = + str.substr(pos_func + 1, pos_left - (pos_func + 1)); + std::wstring func_body = + str.substr(pos_left + 1, pos_right - (pos_left + 1)); + str = str.substr(0, pos_func) + + str.substr(pos_right + 1, str.length() - (pos_right + 1)); + str.insert(pos_func, EvaluateFunction(func_name, func_body)); + i = str.length(); + } + if (open_brackets > 0) + open_brackets--; + } + break; + case '\\': + i++; + break; + } + } + if (!pos_left || !pos_right) + break; + } + } while (pos_func > -1); + + // Unescape + str = UnescapeScriptEntities(str); + + // Clean-up + Replace(str, L"\n\n", L"\n", true); + Replace(str, L" ", L" ", true); + + // Return + return str; +} + +std::wstring EscapeScriptEntities(const std::wstring& str) { + std::wstring escaped; + size_t entity_pos; + + for (size_t pos = 0; pos <= str.length(); ) { + entity_pos = InStrChars(str, L"$,()%\\", pos); + if (entity_pos != -1) { + escaped.append(str, pos, entity_pos - pos); + escaped.append(L"\\"); + escaped.append(str, entity_pos, 1); + } else { + entity_pos = str.length(); + escaped.append(str, pos, entity_pos - pos); + } + pos = entity_pos + 1; + } + + return escaped; +} + +std::wstring UnescapeScriptEntities(const std::wstring& str) { + std::wstring unescaped; + size_t entity_pos; + + for (size_t pos = 0; pos <= str.length(); ) { + entity_pos = InStr(str, L"\\", pos); + if (entity_pos == -1) + entity_pos = str.length(); + unescaped.append(str, pos, entity_pos - pos); + pos = entity_pos + 1; + } + + return unescaped; } \ No newline at end of file diff --git a/src/taiga/script.h b/src/taiga/script.h new file mode 100644 index 000000000..3e0742b56 --- /dev/null +++ b/src/taiga/script.h @@ -0,0 +1,44 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TAIGA_SCRIPT_H +#define TAIGA_TAIGA_SCRIPT_H + +#include + +namespace anime { +class Episode; +} + +void ExecuteAction(std::wstring action, WPARAM wParam = 0, LPARAM lParam = 0); + +std::wstring EvaluateFunction(const std::wstring& func_name, const std::wstring& func_body); + +bool IsScriptFunction(const std::wstring& str); +bool IsScriptVariable(const std::wstring& str); + +std::wstring ReplaceVariables(std::wstring str, + const anime::Episode& episode, + bool url_encode = false, + bool is_manual = false, + bool is_preview = false); + +std::wstring EscapeScriptEntities(const std::wstring& str); +std::wstring UnescapeScriptEntities(const std::wstring& str); + +#endif // TAIGA_TAIGA_SCRIPT_H \ No newline at end of file diff --git a/src/taiga/settings.cpp b/src/taiga/settings.cpp new file mode 100644 index 000000000..0c7d310bb --- /dev/null +++ b/src/taiga/settings.cpp @@ -0,0 +1,512 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/crypto.h" +#include "base/file.h" +#include "base/foreach.h" +#include "base/log.h" +#include "base/string.h" +#include "base/xml.h" +#include "library/anime_db.h" +#include "library/history.h" +#include "library/resource.h" +#include "sync/manager.h" +#include "taiga/path.h" +#include "taiga/settings.h" +#include "taiga/stats.h" +#include "taiga/taiga.h" +#include "taiga/timer.h" +#include "taiga/version.h" +#include "track/media.h" +#include "track/monitor.h" +#include "ui/menu.h" +#include "ui/theme.h" +#include "ui/ui.h" +#include "win/win_registry.h" +#include "win/win_taskdialog.h" + +taiga::AppSettings Settings; + +namespace taiga { + +const std::wstring kDefaultExternalLinks = + L"Hummingboard|http://hummingboard.me\r\n" + L"MALgraph|http://mal.oko.im\r\n" + L"-\r\n" + L"Mahou Showtime Schedule|http://www.mahou.org/Showtime/?o=ET#Current\r\n" + L"The Fansub Wiki|http://www.fansubwiki.com"; +const std::wstring kDefaultFormatHttp = + L"user=%user%" + L"&name=%title%" + L"&ep=%episode%" + L"&eptotal=$if(%total%,%total%,?)" + L"&score=%score%" + L"&picurl=%image%" + L"&playstatus=%playstatus%"; +const std::wstring kDefaultFormatMirc = + L"\00304$if($greater(%episode%,%watched%),Watching,Re-watching):\003 %title%" + L"$if(%episode%, \00303%episode%$if(%total%,/%total%))\003 " + L"$if(%score%,\00314[Score: %score%/10]\003) " + L"\00312%animeurl%"; +const std::wstring kDefaultFormatSkype = + L"Watching: %title%" + L"$if(%episode%, #%episode%$if(%total%,/%total%))"; +const std::wstring kDefaultFormatTwitter = + L"$ifequal(%episode%,%total%,Just completed: %title%" + L"$if(%score%, (Score: %score%/10)) " + L"%animeurl%)"; +const std::wstring kDefaultFormatBalloon = + L"$if(%title%,%title%)\\n" + L"$if(%episode%,Episode %episode%$if(%total%,/%total%) )" + L"$if(%group%,by %group%)\\n" + L"$if(%name%,%name%)"; +const std::wstring kDefaultTorrentAppPath = + L"C:\\Program Files\\uTorrent\\uTorrent.exe"; +const std::wstring kDefaultTorrentSearch = + L"http://www.nyaa.se/?page=rss&cats=1_37&filter=2&term=%title%"; +const std::wstring kDefaultTorrentSource = + L"http://tokyotosho.info/rss.php?filter=1,11&zwnj=0"; + + +//////////////////////////////////////////////////////////////////////////////// + +void AppSettings::InitializeMap() { + if (!map_.empty()) + return; + + #define INITKEY(name, def, path) InitializeKey(name, def, path); + + // Meta + INITKEY(kMeta_Version_Major, nullptr, L"meta/version/major"); + INITKEY(kMeta_Version_Minor, nullptr, L"meta/version/minor"); + INITKEY(kMeta_Version_Revision, nullptr, L"meta/version/revision"); + + // Services + INITKEY(kSync_ActiveService, L"myanimelist", L"account/update/activeservice"); + INITKEY(kSync_AutoOnStart, nullptr, L"account/myanimelist/login"); + INITKEY(kSync_Service_Mal_Username, nullptr, L"account/myanimelist/username"); + INITKEY(kSync_Service_Mal_Password, nullptr, L"account/myanimelist/password"); + INITKEY(kSync_Service_Hummingbird_Username, nullptr, L"account/hummingbird/username"); + INITKEY(kSync_Service_Hummingbird_Password, nullptr, L"account/hummingbird/password"); + + // Library + INITKEY(kLibrary_WatchFolders, L"true", L"anime/folders/watch/enabled"); + + // Application + INITKEY(kApp_List_DoubleClickAction, L"4", L"program/list/action/doubleclick"); + INITKEY(kApp_List_MiddleClickAction, L"3", L"program/list/action/middleclick"); + INITKEY(kApp_List_DisplayEnglishTitles, nullptr, L"program/list/action/englishtitles"); + INITKEY(kApp_List_HighlightNewEpisodes, L"true", L"program/list/filter/episodes/highlight"); + INITKEY(kApp_List_ProgressDisplayAired, L"true", L"program/list/progress/showaired"); + INITKEY(kApp_List_ProgressDisplayAvailable, L"true", L"program/list/progress/showavailable"); + INITKEY(kApp_List_SortColumn, L"0", L"program/list/sort/column"); + INITKEY(kApp_List_SortOrder, L"1", L"program/list/sort/order"); + INITKEY(kApp_Behavior_Autostart, nullptr, L"program/general/autostart"); + INITKEY(kApp_Behavior_StartMinimized, nullptr, L"program/startup/minimize"); + INITKEY(kApp_Behavior_CheckForUpdates, L"true", L"program/startup/checkversion"); + INITKEY(kApp_Behavior_ScanAvailableEpisodes, nullptr, L"program/startup/checkeps"); + INITKEY(kApp_Behavior_CloseToTray, nullptr, L"program/general/close"); + INITKEY(kApp_Behavior_MinimizeToTray, nullptr, L"program/general/minimize"); + INITKEY(kApp_Connection_ProxyHost, nullptr, L"program/proxy/host"); + INITKEY(kApp_Connection_ProxyUsername, nullptr, L"program/proxy/username"); + INITKEY(kApp_Connection_ProxyPassword, nullptr, L"program/proxy/password"); + INITKEY(kApp_Interface_Theme, L"Default", L"program/general/theme"); + INITKEY(kApp_Interface_ExternalLinks, kDefaultExternalLinks.c_str(), L"program/general/externallinks"); + + // Recognition + INITKEY(kSync_Update_Delay, L"120", L"account/update/delay"); + INITKEY(kSync_Update_AskToConfirm, L"true", L"account/update/asktoconfirm"); + INITKEY(kSync_Update_CheckPlayer, nullptr, L"account/update/checkplayer"); + INITKEY(kSync_Update_GoToNowPlaying, L"true", L"account/update/gotonowplaying"); + INITKEY(kSync_Update_OutOfRange, nullptr, L"account/update/outofrange"); + INITKEY(kSync_Update_OutOfRoot, nullptr, L"account/update/outofroot"); + INITKEY(kSync_Update_WaitPlayer, nullptr, L"account/update/waitplayer"); + INITKEY(kSync_Notify_Recognized, L"true", L"program/notifications/balloon/recognized"); + INITKEY(kSync_Notify_NotRecognized, L"true", L"program/notifications/balloon/notrecognized"); + INITKEY(kSync_Notify_Format, kDefaultFormatBalloon.c_str(), L"program/notifications/balloon/format"); + INITKEY(kStream_Ann, nullptr, L"recognition/streaming/providers/ann"); + INITKEY(kStream_Crunchyroll, nullptr, L"recognition/streaming/providers/crunchyroll"); + INITKEY(kStream_Veoh, nullptr, L"recognition/streaming/providers/veoh"); + INITKEY(kStream_Viz, nullptr, L"recognition/streaming/providers/viz"); + INITKEY(kStream_Youtube, nullptr, L"recognition/streaming/providers/youtube"); + + // Sharing + INITKEY(kShare_Http_Enabled, nullptr, L"announce/http/enabled"); + INITKEY(kShare_Http_Format, kDefaultFormatHttp.c_str(), L"announce/http/format"); + INITKEY(kShare_Http_Url, nullptr, L"announce/http/url"); + INITKEY(kShare_Mirc_Enabled, nullptr, L"announce/mirc/enabled"); + INITKEY(kShare_Mirc_MultiServer, nullptr, L"announce/mirc/multiserver"); + INITKEY(kShare_Mirc_UseMeAction, L"true", L"announce/mirc/useaction"); + INITKEY(kShare_Mirc_Mode, L"1", L"announce/mirc/mode"); + INITKEY(kShare_Mirc_Channels, L"#hummingbird, #myanimelist, #taiga", L"announce/mirc/channels"); + INITKEY(kShare_Mirc_Format, kDefaultFormatMirc.c_str(), L"announce/mirc/format"); + INITKEY(kShare_Mirc_Service, L"mIRC", L"announce/mirc/service"); + INITKEY(kShare_Skype_Enabled, nullptr, L"announce/skype/enabled"); + INITKEY(kShare_Skype_Format, kDefaultFormatSkype.c_str(), L"announce/skype/format"); + INITKEY(kShare_Twitter_Enabled, nullptr, L"announce/twitter/enabled"); + INITKEY(kShare_Twitter_Format, kDefaultFormatTwitter.c_str(), L"announce/twitter/format"); + INITKEY(kShare_Twitter_OauthToken, nullptr, L"announce/twitter/oauth_token"); + INITKEY(kShare_Twitter_OauthSecret, nullptr, L"announce/twitter/oauth_secret"); + INITKEY(kShare_Twitter_Username, nullptr, L"announce/twitter/user"); + + // Torrents + INITKEY(kTorrent_Discovery_Source, kDefaultTorrentSource.c_str(), L"rss/torrent/source/address"); + INITKEY(kTorrent_Discovery_SearchUrl, kDefaultTorrentSearch.c_str(), L"rss/torrent/search/address"); + INITKEY(kTorrent_Discovery_AutoCheckEnabled, L"true", L"rss/torrent/options/autocheck"); + INITKEY(kTorrent_Discovery_AutoCheckInterval, L"60", L"rss/torrent/options/checkinterval"); + INITKEY(kTorrent_Discovery_NewAction, L"1", L"rss/torrent/options/newaction"); + INITKEY(kTorrent_Download_AppMode, L"1", L"rss/torrent/application/mode"); + INITKEY(kTorrent_Download_AppPath, nullptr, L"rss/torrent/application/path"); + INITKEY(kTorrent_Download_Location, nullptr, L"rss/torrent/options/downloadpath"); + INITKEY(kTorrent_Download_UseAnimeFolder, L"true", L"rss/torrent/options/autosetfolder"); + INITKEY(kTorrent_Download_FallbackOnFolder, nullptr, L"rss/torrent/options/autousefolder"); + INITKEY(kTorrent_Download_CreateSubfolder, nullptr, L"rss/torrent/options/autocreatefolder"); + INITKEY(kTorrent_Filter_Enabled, L"true", L"rss/torrent/filter/enabled"); + INITKEY(kTorrent_Filter_ArchiveMaxCount, L"1000", L"rss/torrent/filter/archive_maxcount"); + + // Internal + INITKEY(kApp_Position_X, L"-1", L"program/position/x"); + INITKEY(kApp_Position_Y, L"-1", L"program/position/y"); + INITKEY(kApp_Position_W, L"960", L"program/position/w"); + INITKEY(kApp_Position_H, L"640", L"program/position/h"); + INITKEY(kApp_Position_Maximized, nullptr, L"program/position/maximized"); + INITKEY(kApp_Position_Remember, L"true", L"program/exit/remember_pos_size"); + INITKEY(kApp_Option_HideSidebar, nullptr, L"program/general/hidesidebar"); + INITKEY(kApp_Option_EnableRecognition, L"true", L"program/general/enablerecognition"); + INITKEY(kApp_Option_EnableSharing, L"true", L"program/general/enablesharing"); + INITKEY(kApp_Option_EnableSync, L"true", L"program/general/enablesync"); + + #undef INITKEY +} + +//////////////////////////////////////////////////////////////////////////////// + +bool AppSettings::Load() { + xml_document document; + std::wstring path = taiga::GetPath(taiga::kPathSettings); + xml_parse_result result = document.load_file(path.c_str()); + + xml_node settings = document.child(L"settings"); + + InitializeMap(); + + for (enum_t i = kAppSettingNameFirst; i < kAppSettingNameLast; ++i) + ReadValue(settings, i); + + // Meta + if (GetWstr(kMeta_Version_Major).empty()) + Set(kMeta_Version_Major, ToWstr(static_cast(Taiga.version.major))); + if (GetWstr(kMeta_Version_Minor).empty()) + Set(kMeta_Version_Minor, ToWstr(static_cast(Taiga.version.minor))); + if (GetWstr(kMeta_Version_Revision).empty()) + Set(kMeta_Version_Revision, ToWstr(static_cast(Taiga.version.patch))); + + // Folders + root_folders.clear(); + xml_node node_folders = settings.child(L"anime").child(L"folders"); + foreach_xmlnode_(folder, node_folders, L"root") + root_folders.push_back(folder.attribute(L"folder").value()); + + // Anime items + xml_node node_items = settings.child(L"anime").child(L"items"); + foreach_xmlnode_(item, node_items, L"item") { + int anime_id = item.attribute(L"id").as_int(); + auto anime_item = AnimeDatabase.FindItem(anime_id); + if (!anime_item) + anime_item = &AnimeDatabase.items[anime_id]; + anime_item->SetFolder(item.attribute(L"folder").value()); + anime_item->SetUserSynonyms(item.attribute(L"titles").value()); + anime_item->SetUseAlternative(item.attribute(L"use_alternative").as_bool()); + } + + // Media players + xml_node node_players = settings.child(L"recognition").child(L"mediaplayers"); + foreach_xmlnode_(player, node_players, L"player") { + std::wstring name = player.attribute(L"name").value(); + bool enabled = player.attribute(L"enabled").as_bool(); + foreach_(it, MediaPlayers.items) { + if (it->name == name) { + it->enabled = enabled; + break; + } + } + } + + // Torrent application path + if (GetWstr(kTorrent_Download_AppPath).empty()) { + Set(kTorrent_Download_AppPath, GetDefaultAppPath(L".torrent", kDefaultTorrentAppPath)); + } + + // Torrent filters + xml_node node_filter = settings.child(L"rss").child(L"torrent").child(L"filter"); + Aggregator.filter_manager.filters.clear(); + foreach_xmlnode_(item, node_filter, L"item") { + Aggregator.filter_manager.AddFilter( + static_cast( + Aggregator.filter_manager.GetIndexFromShortcode( + kFeedFilterShortcodeAction, item.attribute(L"action").value())), + static_cast( + Aggregator.filter_manager.GetIndexFromShortcode( + kFeedFilterShortcodeMatch, item.attribute(L"match").value())), + static_cast( + Aggregator.filter_manager.GetIndexFromShortcode( + kFeedFilterShortcodeOption, item.attribute(L"option").value())), + item.attribute(L"enabled").as_bool(), + item.attribute(L"name").value()); + foreach_xmlnode_(anime, item, L"anime") { + auto& filter = Aggregator.filter_manager.filters.back(); + filter.anime_ids.push_back(anime.attribute(L"id").as_int()); + } + foreach_xmlnode_(condition, item, L"condition") { + Aggregator.filter_manager.filters.back().AddCondition( + static_cast( + Aggregator.filter_manager.GetIndexFromShortcode( + kFeedFilterShortcodeElement, + condition.attribute(L"element").value())), + static_cast( + Aggregator.filter_manager.GetIndexFromShortcode( + kFeedFilterShortcodeOperator, + condition.attribute(L"operator").value())), + condition.attribute(L"value").value()); + } + } + + if (Aggregator.filter_manager.filters.empty()) + Aggregator.filter_manager.AddPresets(); + auto feed = Aggregator.Get(kFeedCategoryLink); + if (feed) + feed->link = GetWstr(kTorrent_Discovery_Source); + Aggregator.LoadArchive(); + + return result.status == pugi::status_ok; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool AppSettings::Save() { + xml_document document; + xml_node settings = document.append_child(L"settings"); + + // Meta + Set(kMeta_Version_Major, ToWstr(static_cast(Taiga.version.major))); + Set(kMeta_Version_Minor, ToWstr(static_cast(Taiga.version.minor))); + Set(kMeta_Version_Revision, ToWstr(static_cast(Taiga.version.patch))); + + for (enum_t i = kAppSettingNameFirst; i < kAppSettingNameLast; ++i) + WriteValue(settings, i); + + // Root folders + xml_node folders = settings.child(L"anime").child(L"folders"); + foreach_(it, root_folders) { + xml_node root = folders.append_child(L"root"); + root.append_attribute(L"folder") = it->c_str(); + } + + // Anime items + xml_node items = settings.child(L"anime").append_child(L"items"); + foreach_(it, AnimeDatabase.items) { + anime::Item& anime_item = it->second; + if (anime_item.GetFolder().empty() && + !anime_item.UserSynonymsAvailable() && + !anime_item.GetUseAlternative()) + continue; + xml_node item = items.append_child(L"item"); + item.append_attribute(L"id") = anime_item.GetId(); + if (!anime_item.GetFolder().empty()) + item.append_attribute(L"folder") = anime_item.GetFolder().c_str(); + if (anime_item.UserSynonymsAvailable()) + item.append_attribute(L"titles") = Join(anime_item.GetUserSynonyms(), L"; ").c_str(); + if (anime_item.GetUseAlternative()) + item.append_attribute(L"use_alternative") = anime_item.GetUseAlternative(); + } + + // Media players + xml_node mediaplayers = settings.child(L"recognition").append_child(L"mediaplayers"); + foreach_(it, MediaPlayers.items) { + xml_node player = mediaplayers.append_child(L"player"); + player.append_attribute(L"name") = it->name.c_str(); + player.append_attribute(L"enabled") = it->enabled; + } + + // Torrent filters + xml_node torrent_filter = settings.child(L"rss").child(L"torrent").child(L"filter"); + foreach_(it, Aggregator.filter_manager.filters) { + xml_node item = torrent_filter.append_child(L"item"); + item.append_attribute(L"action") = + Aggregator.filter_manager.GetShortcodeFromIndex( + kFeedFilterShortcodeAction, it->action).c_str(); + item.append_attribute(L"match") = + Aggregator.filter_manager.GetShortcodeFromIndex( + kFeedFilterShortcodeMatch, it->match).c_str(); + item.append_attribute(L"option") = + Aggregator.filter_manager.GetShortcodeFromIndex( + kFeedFilterShortcodeOption, it->option).c_str(); + item.append_attribute(L"enabled") = it->enabled; + item.append_attribute(L"name") = it->name.c_str(); + foreach_(ita, it->anime_ids) { + xml_node anime = item.append_child(L"anime"); + anime.append_attribute(L"id") = *ita; + } + foreach_(itc, it->conditions) { + xml_node condition = item.append_child(L"condition"); + condition.append_attribute(L"element") = + Aggregator.filter_manager.GetShortcodeFromIndex( + kFeedFilterShortcodeElement, itc->element).c_str(); + condition.append_attribute(L"operator") = + Aggregator.filter_manager.GetShortcodeFromIndex( + kFeedFilterShortcodeOperator, itc->op).c_str(); + condition.append_attribute(L"value") = itc->value.c_str(); + } + } + + // Write to registry + win::Registry reg; + reg.OpenKey(HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", + 0, KEY_SET_VALUE); + if (GetBool(kApp_Behavior_Autostart)) { + std::wstring app_path = Taiga.GetModulePath(); + reg.SetValue(TAIGA_APP_NAME, app_path.c_str()); + } else { + reg.DeleteValue(TAIGA_APP_NAME); + } + reg.CloseKey(); + + std::wstring path = taiga::GetPath(taiga::kPathSettings); + return XmlWriteDocumentToFile(document, path); +} + +//////////////////////////////////////////////////////////////////////////////// + +void AppSettings::ApplyChanges(const std::wstring& previous_service, + const std::wstring& previous_user, + const std::wstring& previous_theme) { + bool changed_service = GetWstr(kSync_ActiveService) != previous_service; + if (changed_service) { + if (History.queue.GetItemCount() > 0) { + ui::OnSettingsServiceChangeFailed(); + Set(kSync_ActiveService, previous_service); + changed_service = false; + } else if (!previous_user.empty()) { + if (ui::OnSettingsServiceChangeConfirm(previous_service, + GetWstr(kSync_ActiveService))) { + std::wstring current_service = GetWstr(kSync_ActiveService); + Set(kSync_ActiveService, previous_service); + AnimeDatabase.SaveList(true); + Set(kSync_ActiveService, current_service); + AnimeDatabase.items.clear(); + ImageDatabase.Clear(); + } else { + Set(kSync_ActiveService, previous_service); + changed_service = false; + } + } + } + + bool changed_theme = GetWstr(kApp_Interface_Theme) != previous_theme; + if (changed_theme) { + ui::Theme.Load(); + ui::OnSettingsThemeChange(); + } + + bool changed_username = GetCurrentUsername() != previous_user; + if (changed_username || changed_service) { + AnimeDatabase.LoadList(); + History.Load(); + CurrentEpisode.Set(anime::ID_UNKNOWN); + Stats.CalculateAll(); + Taiga.logged_in = false; + ui::OnSettingsUserChange(); + ui::OnSettingsServiceChange(); + } else { + ui::OnSettingsChange(); + } + + bool enable_monitor = GetBool(kLibrary_WatchFolders); + FolderMonitor.Enable(enable_monitor); + + ui::Menus.UpdateExternalLinks(); + + timers.UpdateIntervalsFromSettings(); +} + +void AppSettings::HandleCompatibility() { + // Nothing to do here, for now +} + +void AppSettings::RestoreDefaults() { + // Take a backup + std::wstring file = taiga::GetPath(taiga::kPathSettings); + std::wstring backup = file + L".bak"; + DWORD flags = MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH; + MoveFileEx(file.c_str(), backup.c_str(), flags); + + // Reload settings + std::wstring previous_service = GetCurrentService()->canonical_name(); + std::wstring previous_user = GetCurrentUsername(); + std::wstring previous_theme = GetWstr(kApp_Interface_Theme); + Load(); + ApplyChanges(previous_service, previous_user, previous_theme); + + // Reload settings dialog + ui::OnSettingsRestoreDefaults(); +} + +//////////////////////////////////////////////////////////////////////////////// + +const sync::Service* GetCurrentService() { + std::wstring service_name = Settings[kSync_ActiveService]; + return ServiceManager.service(service_name); +} + +sync::ServiceId GetCurrentServiceId() { + auto service = GetCurrentService(); + + if (service) + return static_cast(service->id()); + + return sync::kMyAnimeList; +} + +const std::wstring GetCurrentUsername() { + std::wstring username; + auto service = GetCurrentService(); + + if (service->id() == sync::kMyAnimeList) { + username = Settings[kSync_Service_Mal_Username]; + } else if (service->id() == sync::kHummingbird) { + username = Settings[kSync_Service_Hummingbird_Username]; + } + + return username; +} + +const std::wstring GetCurrentPassword() { + std::wstring password; + auto service = GetCurrentService(); + + if (service->id() == sync::kMyAnimeList) { + password = SimpleDecrypt(Settings[kSync_Service_Mal_Password]); + } else if (service->id() == sync::kHummingbird) { + password = SimpleDecrypt(Settings[kSync_Service_Hummingbird_Password]); + } + + return password; +} + +} // namespace taiga \ No newline at end of file diff --git a/src/taiga/settings.h b/src/taiga/settings.h new file mode 100644 index 000000000..dd45207f2 --- /dev/null +++ b/src/taiga/settings.h @@ -0,0 +1,167 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TAIGA_SETTINGS_H +#define TAIGA_TAIGA_SETTINGS_H + +#include +#include +#include + +#include "base/settings.h" + +namespace sync { +class Service; +enum ServiceId; +} + +namespace taiga { + +enum AppSettingName { + kAppSettingNameFirst = 0, // used for iteration + + // Meta + kMeta_Version_Major = 0, + kMeta_Version_Minor, + kMeta_Version_Revision, + + // Services + kSync_ActiveService, + kSync_AutoOnStart, + kSync_Service_Mal_Username, + kSync_Service_Mal_Password, + kSync_Service_Hummingbird_Username, + kSync_Service_Hummingbird_Password, + + // Library + kLibrary_WatchFolders, + + // Application + kApp_List_DoubleClickAction, + kApp_List_MiddleClickAction, + kApp_List_DisplayEnglishTitles, + kApp_List_HighlightNewEpisodes, + kApp_List_ProgressDisplayAired, + kApp_List_ProgressDisplayAvailable, + kApp_List_SortColumn, + kApp_List_SortOrder, + kApp_Behavior_Autostart, + kApp_Behavior_StartMinimized, + kApp_Behavior_CheckForUpdates, + kApp_Behavior_ScanAvailableEpisodes, + kApp_Behavior_CloseToTray, + kApp_Behavior_MinimizeToTray, + kApp_Connection_ProxyHost, + kApp_Connection_ProxyUsername, + kApp_Connection_ProxyPassword, + kApp_Interface_Theme, + kApp_Interface_ExternalLinks, + + // Recognition + kSync_Update_Delay, + kSync_Update_AskToConfirm, + kSync_Update_CheckPlayer, + kSync_Update_GoToNowPlaying, + kSync_Update_OutOfRange, + kSync_Update_OutOfRoot, + kSync_Update_WaitPlayer, + kSync_Notify_Recognized, + kSync_Notify_NotRecognized, + kSync_Notify_Format, + kStream_Ann, + kStream_Crunchyroll, + kStream_Veoh, + kStream_Viz, + kStream_Youtube, + + // Sharing + kShare_Http_Enabled, + kShare_Http_Format, + kShare_Http_Url, + kShare_Mirc_Enabled, + kShare_Mirc_MultiServer, + kShare_Mirc_UseMeAction, + kShare_Mirc_Mode, + kShare_Mirc_Channels, + kShare_Mirc_Format, + kShare_Mirc_Service, + kShare_Skype_Enabled, + kShare_Skype_Format, + kShare_Twitter_Enabled, + kShare_Twitter_Format, + kShare_Twitter_OauthToken, + kShare_Twitter_OauthSecret, + kShare_Twitter_Username, + + // Torrents + kTorrent_Discovery_Source, + kTorrent_Discovery_SearchUrl, + kTorrent_Discovery_AutoCheckEnabled, + kTorrent_Discovery_AutoCheckInterval, + kTorrent_Discovery_NewAction, + kTorrent_Download_AppMode, + kTorrent_Download_AppPath, + kTorrent_Download_Location, + kTorrent_Download_UseAnimeFolder, + kTorrent_Download_FallbackOnFolder, + kTorrent_Download_CreateSubfolder, + kTorrent_Filter_Enabled, + kTorrent_Filter_ArchiveMaxCount, + + // Internal + kApp_Position_X, + kApp_Position_Y, + kApp_Position_W, + kApp_Position_H, + kApp_Position_Maximized, + kApp_Position_Remember, + kApp_Option_HideSidebar, + kApp_Option_EnableRecognition, + kApp_Option_EnableSharing, + kApp_Option_EnableSync, + + kAppSettingNameLast // used for iteration +}; + +class AppSettings : public base::Settings { +public: + bool Load(); + bool Save(); + + void ApplyChanges(const std::wstring& previous_service, + const std::wstring& previous_user, + const std::wstring& previous_theme); + void HandleCompatibility(); + void RestoreDefaults(); + + std::vector root_folders; + +private: + void InitializeMap(); +}; + +const sync::Service* GetCurrentService(); +sync::ServiceId GetCurrentServiceId(); +const std::wstring GetCurrentUsername(); +const std::wstring GetCurrentPassword(); + +} // namespace taiga + +extern taiga::AppSettings Settings; + +#endif // TAIGA_TAIGA_SETTINGS_H \ No newline at end of file diff --git a/stats.cpp b/src/taiga/stats.cpp similarity index 55% rename from stats.cpp rename to src/taiga/stats.cpp index e2382c098..a5f703169 100644 --- a/stats.cpp +++ b/src/taiga/stats.cpp @@ -1,183 +1,198 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "stats.h" - -#include "anime_db.h" -#include "common.h" -#include "foreach.h" -#include "myanimelist.h" -#include "string.h" -#include "taiga.h" - -Statistics Stats; - -// ============================================================================= - -Statistics::Statistics() - : anime_count(0), - connections_failed(0), - connections_succeeded(0), - episode_count(0), - image_count(0), - image_size(0), - score_mean(0.0f), - score_deviation(0.0f), - score_count(11, 0), - score_distribution(11, 0.0f), - tigers_harmed(0), - torrent_count(0), - torrent_size(0), - uptime(0) { -} - -// ============================================================================= - -void Statistics::CalculateAll() { - CalculateAnimeCount(); - CalculateEpisodeCount(); - CalculateLifeSpentWatching(); - CalculateLocalData(); - CalculateMeanScore(); - CalculateScoreDeviation(); - CalculateScoreDistribution(); -} - -int Statistics::CalculateAnimeCount() { - anime_count = 0; - - for (auto it = AnimeDatabase.items.begin(); it != AnimeDatabase.items.end(); ++it) - if (it->second.IsInList()) - anime_count++; - - return anime_count; -} - -int Statistics::CalculateEpisodeCount() { - episode_count = 0; - - for (auto it = AnimeDatabase.items.begin(); it != AnimeDatabase.items.end(); ++it) { - if (!it->second.IsInList()) continue; - episode_count += it->second.GetMyLastWatchedEpisode(); - // TODO: Implement times_rewatched when MAL adds to API - if (it->second.GetMyRewatching() == TRUE) - episode_count += it->second.GetEpisodeCount(); - } - - return episode_count; -} - -wstring Statistics::CalculateLifeSpentWatching() { - int duration, seconds = 0; - - for (auto it = AnimeDatabase.items.begin(); it != AnimeDatabase.items.end(); ++it) { - if (!it->second.IsInList()) continue; - // Approximate duration in minutes - switch (it->second.GetType()) { - default: - case mal::TYPE_TV: duration = 24; break; - case mal::TYPE_OVA: duration = 24; break; - case mal::TYPE_MOVIE: duration = 90; break; - case mal::TYPE_SPECIAL: duration = 12; break; - case mal::TYPE_ONA: duration = 24; break; - case mal::TYPE_MUSIC: duration = 5; break; - } - int episodes_watched = it->second.GetMyLastWatchedEpisode(); - if (it->second.GetMyRewatching() == TRUE) - episodes_watched += it->second.GetEpisodeCount(); - seconds += (duration * 60) * episodes_watched; - } - - if (seconds > 0) { - life_spent_watching = ToDateString(seconds); - } else { - life_spent_watching = L"None"; - } - - return life_spent_watching; -} - -void Statistics::CalculateLocalData() { - vector file_list; - - image_count = PopulateFiles(file_list, anime::GetImagePath()); - image_size = GetFolderSize(anime::GetImagePath(), false); - - file_list.clear(); - torrent_count = PopulateFiles(file_list, Taiga.GetDataPath() + L"feed\\", L"torrent", true); - torrent_size = GetFolderSize(Taiga.GetDataPath() + L"feed\\", true); -} - -float Statistics::CalculateMeanScore() { - float sum_scores = 0.0f, items_scored = 0.0f; - - for (auto it = AnimeDatabase.items.begin(); it != AnimeDatabase.items.end(); ++it) { - if (!it->second.IsInList()) continue; - if (it->second.GetMyScore() > 0) { - sum_scores += static_cast(it->second.GetMyScore()); - items_scored++; - } - } - - score_mean = items_scored > 0 ? sum_scores / items_scored : 0.0f; - - return score_mean; -} - -float Statistics::CalculateScoreDeviation() { - float sum_squares = 0.0f, items_scored = 0.0f; - - for (auto it = AnimeDatabase.items.begin(); it != AnimeDatabase.items.end(); ++it) { - if (!it->second.IsInList()) continue; - if (it->second.GetMyScore() > 0) { - sum_squares += pow(static_cast(it->second.GetMyScore()) - score_mean, 2); - items_scored++; - } - } - - score_deviation = items_scored > 0 ? sqrt(sum_squares / items_scored) : 0.0f; - - return score_deviation; -} - -vector Statistics::CalculateScoreDistribution() { - int score = 0; - float extreme_value = 1.0f; - - foreach_(item, score_count) - *item = 0; - foreach_(item, score_distribution) - *item = 0.0f; - - for (auto it = AnimeDatabase.items.begin(); it != AnimeDatabase.items.end(); ++it) { - score = it->second.GetMyScore(); - if (score > 0) { - score_count[score]++; - score_distribution[score]++; - extreme_value = max(score_distribution[score], extreme_value); - } - } - - for (auto it = score_distribution.begin(); it != score_distribution.end(); ++it) { - *it = *it / extreme_value; - } - - return score_distribution; -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/file.h" +#include "base/foreach.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "taiga/path.h" +#include "taiga/stats.h" + +taiga::Statistics Stats; + +namespace taiga { + +Statistics::Statistics() + : anime_count(0), + connections_failed(0), + connections_succeeded(0), + episode_count(0), + image_count(0), + image_size(0), + score_mean(0.0f), + score_deviation(0.0f), + score_count(11, 0), + score_distribution(11, 0.0f), + tigers_harmed(0), + torrent_count(0), + torrent_size(0), + uptime(0) { +} + +void Statistics::CalculateAll() { + CalculateAnimeCount(); + CalculateEpisodeCount(); + CalculateLifeSpentWatching(); + CalculateLocalData(); + CalculateMeanScore(); + CalculateScoreDeviation(); + CalculateScoreDistribution(); +} + +int Statistics::CalculateAnimeCount() { + anime_count = 0; + + foreach_(it, AnimeDatabase.items) + if (it->second.IsInList()) + anime_count++; + + return anime_count; +} + +int Statistics::CalculateEpisodeCount() { + episode_count = 0; + + foreach_(it, AnimeDatabase.items) { + if (!it->second.IsInList()) + continue; + + episode_count += it->second.GetMyLastWatchedEpisode(); + + // TODO: Implement times_rewatched when MAL adds to API + if (it->second.GetMyRewatching() == TRUE) + episode_count += it->second.GetEpisodeCount(); + } + + return episode_count; +} + +const std::wstring& Statistics::CalculateLifeSpentWatching() { + int duration = 0; + int seconds = 0; + + foreach_(it, AnimeDatabase.items) { + if (!it->second.IsInList()) + continue; + + duration = it->second.GetEpisodeLength(); + if (duration <= 0) { + // Approximate duration in minutes + switch (it->second.GetType()) { + default: + case anime::kTv: duration = 24; break; + case anime::kOva: duration = 24; break; + case anime::kMovie: duration = 90; break; + case anime::kSpecial: duration = 12; break; + case anime::kOna: duration = 24; break; + case anime::kMusic: duration = 5; break; + } + } + + int episodes_watched = it->second.GetMyLastWatchedEpisode(); + + if (it->second.GetMyRewatching() == TRUE) + episodes_watched += it->second.GetEpisodeCount(); + + seconds += (duration * 60) * episodes_watched; + } + + if (seconds > 0) { + life_spent_watching = ToDateString(seconds); + } else { + life_spent_watching = L"None"; + } + + return life_spent_watching; +} + +void Statistics::CalculateLocalData() { + std::vector file_list; + + image_count = PopulateFiles(file_list, anime::GetImagePath()); + image_size = GetFolderSize(anime::GetImagePath(), false); + + file_list.clear(); + std::wstring path = taiga::GetPath(taiga::kPathFeed); + torrent_count = PopulateFiles(file_list, path, L"torrent", true); + + torrent_size = GetFolderSize(path, true); +} + +float Statistics::CalculateMeanScore() { + float items_scored = 0.0f; + float sum_scores = 0.0f; + + foreach_(it, AnimeDatabase.items) { + if (!it->second.IsInList()) + continue; + + if (it->second.GetMyScore() > 0) { + sum_scores += static_cast(it->second.GetMyScore()); + items_scored++; + } + } + + score_mean = items_scored > 0 ? (sum_scores / items_scored) : 0.0f; + + return score_mean; +} + +float Statistics::CalculateScoreDeviation() { + float items_scored = 0.0f; + float sum_squares = 0.0f; + + foreach_(it, AnimeDatabase.items) { + if (!it->second.IsInList()) + continue; + + if (it->second.GetMyScore() > 0) { + float score = static_cast(it->second.GetMyScore()); + sum_squares += pow(score - score_mean, 2); + items_scored++; + } + } + + score_deviation = items_scored > 0 ? sqrt(sum_squares / items_scored) : 0.0f; + + return score_deviation; +} + +const std::vector& Statistics::CalculateScoreDistribution() { + foreach_(item, score_count) + *item = 0; + foreach_(item, score_distribution) + *item = 0.0f; + + float extreme_value = 1.0f; + + foreach_(it, AnimeDatabase.items) { + int score = it->second.GetMyScore(); + if (score > 0) { + score_count[score]++; + score_distribution[score]++; + extreme_value = max(score_distribution[score], extreme_value); + } + } + + foreach_(it, score_distribution) + *it = *it / extreme_value; + + return score_distribution; +} + +} // namespace taiga \ No newline at end of file diff --git a/stats.h b/src/taiga/stats.h similarity index 68% rename from stats.h rename to src/taiga/stats.h index 6e75c7d7b..1cb188b30 100644 --- a/stats.h +++ b/src/taiga/stats.h @@ -1,60 +1,63 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef STATS_H -#define STATS_H - -#include "std.h" - -// ============================================================================= - -class Statistics { -public: - Statistics(); - virtual ~Statistics() {} - - void CalculateAll(); - int CalculateAnimeCount(); - int CalculateEpisodeCount(); - wstring CalculateLifeSpentWatching(); - void CalculateLocalData(); - float CalculateMeanScore(); - float CalculateScoreDeviation(); - vector CalculateScoreDistribution(); - -public: - int anime_count; - int connections_failed; - int connections_succeeded; - int episode_count; - int image_count; - int image_size; - wstring life_spent_watching; - float score_mean; - float score_deviation; - vector score_count; - vector score_distribution; - int tigers_harmed; - int torrent_count; - int torrent_size; - int uptime; -}; - -extern Statistics Stats; - -#endif // STATS_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TAIGA_STATS_H +#define TAIGA_TAIGA_STATS_H + +#include +#include + +namespace taiga { + +class Statistics { +public: + Statistics(); + ~Statistics() {} + + void CalculateAll(); + int CalculateAnimeCount(); + int CalculateEpisodeCount(); + const std::wstring& CalculateLifeSpentWatching(); + void CalculateLocalData(); + float CalculateMeanScore(); + float CalculateScoreDeviation(); + const std::vector& CalculateScoreDistribution(); + +public: + int anime_count; + int connections_failed; + int connections_succeeded; + int episode_count; + int image_count; + int image_size; + std::wstring life_spent_watching; + float score_mean; + float score_deviation; + std::vector score_count; + std::vector score_distribution; + int tigers_harmed; + int torrent_count; + int torrent_size; + int uptime; +}; + +} // namespace taiga + +extern taiga::Statistics Stats; + +#endif // TAIGA_TAIGA_STATS_H \ No newline at end of file diff --git a/src/taiga/taiga.cpp b/src/taiga/taiga.cpp new file mode 100644 index 000000000..e99cd15a0 --- /dev/null +++ b/src/taiga/taiga.cpp @@ -0,0 +1,162 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/log.h" +#include "base/process.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/history.h" +#include "taiga/announce.h" +#include "taiga/api.h" +#include "taiga/dummy.h" +#include "taiga/resource.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" +#include "taiga/version.h" +#include "track/media.h" +#include "ui/dialog.h" +#include "ui/menu.h" +#include "ui/theme.h" +#include "win/win_taskbar.h" + +taiga::App Taiga; + +namespace taiga { + +App::App() + : debug_mode(false), + logged_in(false), + current_tip_type(kTipTypeDefault), + play_status(kPlayStatusStopped) { +#ifdef _DEBUG + debug_mode = true; +#endif + + version.major = TAIGA_VERSION_MAJOR; + version.minor = TAIGA_VERSION_MINOR; + version.patch = TAIGA_VERSION_PATCH; + version.prerelease_identifiers = TAIGA_VERSION_PRE; + if (TAIGA_VERSION_BUILD > 0) + version.build_metadata = ToWstr(TAIGA_VERSION_BUILD); +} + +App::~App() { + OleUninitialize(); +} + +BOOL App::InitInstance() { + // Check another instance + if (CheckInstance(L"Taiga-33d5a63c-de90-432f-9a8b-f6f733dab258", + L"TaigaMainW")) + return FALSE; + + // Initialize + InitCommonControls(ICC_STANDARD_CLASSES); + OleInitialize(nullptr); + + // Initialize logger + Logger.SetOutputPath(AddTrailingSlash(GetPathOnly(GetModulePath())) + + TAIGA_APP_NAME L".log"); +#ifdef _DEBUG + Logger.SetSeverityLevel(LevelDebug); +#else + Logger.SetSeverityLevel(LevelWarning); +#endif + LOG(LevelInformational, L"Version " + std::wstring(version)); + + // Parse command line + ParseCommandLineArguments(); + + // Load data + LoadData(); + + DummyAnime.Initialize(); + DummyEpisode.Initialize(); + + // Create API windows + ::Skype.Create(); + TaigaApi.Create(); + + if (Settings.GetBool(kApp_Behavior_CheckForUpdates)) { + ui::ShowDialog(ui::kDialogUpdate); + } else { + ui::ShowDialog(ui::kDialogMain); + } + + return TRUE; +} + +void App::Uninitialize() { + // Announce + if (play_status == kPlayStatusPlaying) { + play_status = kPlayStatusStopped; + ::Announcer.Do(kAnnounceToHttp); + } + ::Announcer.Clear(kAnnounceToSkype); + + // Cleanup + ConnectionManager.Shutdown(); + Taskbar.Destroy(); + TaskbarList.Release(); + + // Save + Settings.Save(); + AnimeDatabase.SaveDatabase(); + Aggregator.SaveArchive(); + + // Exit + PostQuitMessage(); +} + +void App::ParseCommandLineArguments() { + int argument_count = 0; + LPWSTR* argument_list = CommandLineToArgvW(GetCommandLine(), &argument_count); + + if (!argument_count || !argument_list) + return; + + for (int i = 1; i < argument_count; i++) { + std::wstring argument = argument_list[i]; + + if (argument == L"-debug" && !debug_mode) { + debug_mode = true; + Logger.SetSeverityLevel(LevelDebug); + LOG(LevelDebug, argument); + } + } + + LocalFree(argument_list); +} + +void App::LoadData() { + MediaPlayers.Load(); + + if (Settings.Load()) + Settings.HandleCompatibility(); + + ui::Theme.Load(); + ui::Menus.Load(); + + AnimeDatabase.LoadDatabase(); + AnimeDatabase.LoadList(); + AnimeDatabase.ClearInvalidItems(); + + History.Load(); +} + +} // namespace taiga \ No newline at end of file diff --git a/taiga.h b/src/taiga/taiga.h similarity index 50% rename from taiga.h rename to src/taiga/taiga.h index b8acb4315..d0b037f86 100644 --- a/taiga.h +++ b/src/taiga/taiga.h @@ -1,86 +1,80 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef TAIGA_H -#define TAIGA_H - -#include "std.h" -#include "update.h" -#include "version.h" - -#include "win32/win_main.h" - -#define APP_NAME L"Taiga" -#define APP_TITLE L"Taiga 1.0" -#define APP_VERSION L"1.0.282" -#define APP_BUILD L"2014-03-21" - -#ifndef PORTABLE -#define PORTABLE -#endif - -enum PlayStatus { - PLAYSTATUS_STOPPED, - PLAYSTATUS_PLAYING, - PLAYSTATUS_UPDATED -}; - -enum TipType { - TIPTYPE_DEFAULT, - TIPTYPE_NOWPLAYING, - TIPTYPE_SEARCH, - TIPTYPE_TORRENT, - TIPTYPE_UPDATEFAILED -}; - -// ============================================================================= - -class Taiga : public win32::App { -public: - Taiga(); - ~Taiga(); - - BOOL InitInstance(); - void Uninitialize(); - -public: - wstring GetDataPath(); - void LoadData(); - -public: - int current_tip_type, play_status; - bool logged_in; - int ticker_media, ticker_memory, ticker_new_episodes, ticker_queue; - - class Updater : public UpdateHelper { - public: - Updater() {} - virtual ~Updater() {} - - void OnCheck(); - void OnCRCCheck(const wstring& path, wstring& crc); - void OnDone(); - void OnProgress(int file_index); - bool OnRestartApp(); - void OnRunActions(); - } Updater; -}; - -extern class Taiga Taiga; - -#endif // TAIGA_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TAIGA_TAIGA_H +#define TAIGA_TAIGA_TAIGA_H + +#include "base/version.h" +#include "taiga/update.h" +#include "win/win_main.h" + +#define TAIGA_APP_NAME L"Taiga" +#define TAIGA_APP_TITLE L"Taiga" + +#define TAIGA_PORTABLE + +namespace taiga { + +enum PlayStatus { + kPlayStatusStopped, + kPlayStatusPlaying, + kPlayStatusUpdated +}; + +enum TipType { + kTipTypeDefault, + kTipTypeNowPlaying, + kTipTypeSearch, + kTipTypeTorrent, + kTipTypeUpdateFailed +}; + +class App : public win::App { +public: + App(); + ~App(); + + BOOL InitInstance(); + void Uninitialize(); + + void LoadData(); + + int current_tip_type, play_status; + bool debug_mode; + bool logged_in; + base::SemanticVersion version; + + class Updater : public UpdateHelper { + public: + void OnCheck(); + void OnCRCCheck(const std::wstring& path, std::wstring& crc); + void OnDone(); + void OnProgress(int file_index); + bool OnRestartApp(); + void OnRunActions(); + } Updater; + +private: + void ParseCommandLineArguments(); +}; + +} // namespace taiga + +extern taiga::App Taiga; + +#endif // TAIGA_TAIGA_TAIGA_H diff --git a/src/taiga/timer.cpp b/src/taiga/timer.cpp new file mode 100644 index 000000000..5db455a9c --- /dev/null +++ b/src/taiga/timer.cpp @@ -0,0 +1,172 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "base/log.h" +#include "base/string.h" +#include "library/anime.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "library/history.h" +#include "library/resource.h" +#include "taiga/announce.h" +#include "taiga/http.h" +#include "taiga/settings.h" +#include "taiga/stats.h" +#include "taiga/timer.h" +#include "track/feed.h" +#include "track/media.h" +#include "track/search.h" +#include "ui/dlg/dlg_main.h" +#include "ui/dlg/dlg_stats.h" +#include "ui/dlg/dlg_torrent.h" + +namespace taiga { + +Timer timer_history(kTimerHistory, 5 * 60); // 5 minutes +Timer timer_library(kTimerLibrary, 30 * 60); // 30 minutes +Timer timer_media(kTimerMedia, 2 * 60, false); // 2 minutes +Timer timer_memory(kTimerMemory, 10 * 60); // 10 minutes +Timer timer_stats(kTimerStats, 10); // 10 seconds +Timer timer_torrents(kTimerTorrents, 60 * 60); // 60 minutes + +TimerManager timers; + +//////////////////////////////////////////////////////////////////////////////// + +Timer::Timer(unsigned int id, int interval, bool repeat) + : base::Timer(id, interval, repeat) { +} + +void Timer::OnTimeout() { + LOG(LevelDebug, L"ID: " + ToWstr(static_cast(id())) + L", " + L"Interval: " + ToWstr(static_cast(this->interval()))); + + switch (id()) { + case kTimerHistory: + if (!History.queue.updating) + History.queue.Check(true); + break; + + case kTimerLibrary: + ScanAvailableEpisodesQuick(); + break; + + case kTimerMedia: + ::Announcer.Do(taiga::kAnnounceToHttp | taiga::kAnnounceToMirc | + taiga::kAnnounceToSkype); + if (!Settings.GetBool(taiga::kSync_Update_WaitPlayer)) { + auto anime_item = AnimeDatabase.FindItem(CurrentEpisode.anime_id); + if (anime_item) + anime::UpdateList(*anime_item, CurrentEpisode); + } + break; + + case kTimerMemory: + ConnectionManager.FreeMemory(); + ImageDatabase.FreeMemory(); + break; + + case kTimerStats: + Stats.CalculateAll(); + break; + + case kTimerTorrents: + Aggregator.feeds.at(0).Check( + Settings[taiga::kTorrent_Discovery_Source], true); + break; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void TimerManager::Initialize() { + // Set intervals based on user settings + UpdateIntervalsFromSettings(); + + // Initialize manager + base::TimerManager::Initialize(nullptr, TimerProc); + + // Attach timers to the manager + InsertTimer(&timer_history); + InsertTimer(&timer_library); + InsertTimer(&timer_media); + InsertTimer(&timer_memory); + InsertTimer(&timer_stats); + InsertTimer(&timer_torrents); +} + +void TimerManager::UpdateEnabledState() { + // Library + timer_library.set_enabled(!Settings.GetBool(taiga::kLibrary_WatchFolders)); + + // Media + auto media_player = MediaPlayers.GetRunningPlayer(); + bool media_player_is_running = media_player != nullptr; + bool media_player_is_active = media_player && media_player->IsActive(); + bool episode_processed = CurrentEpisode.processed || timer_media.ticks() == 0; + timer_media.set_enabled(media_player_is_running && media_player_is_active && + !episode_processed); + + // Statistics + timer_stats.set_enabled(ui::DlgStats.IsVisible() != FALSE); + + // Torrents + timer_torrents.set_enabled( + Settings.GetBool(taiga::kTorrent_Discovery_AutoCheckEnabled)); +} + +void TimerManager::UpdateIntervalsFromSettings() { + timer_media.set_interval( + Settings.GetInt(taiga::kSync_Update_Delay)); + + timer_torrents.set_interval( + Settings.GetInt(taiga::kTorrent_Discovery_AutoCheckInterval) * 60); +} + +void TimerManager::UpdateUi() { + // Media + ui::DlgMain.UpdateStatusTimer(); + ProcessMediaPlayerStatus(MediaPlayers.GetRunningPlayer()); + + // Statistics + if (ui::DlgStats.IsVisible()) + ui::DlgStats.Refresh(); + + // Torrents + ui::DlgTorrent.SetTimer(timer_torrents.ticks()); +} + +void TimerManager::OnTick() { + MediaPlayers.CheckRunningPlayers(); + Stats.uptime++; + + UpdateEnabledState(); + + foreach_(it, timers_) + it->second->Tick(); + + UpdateUi(); +} + +void CALLBACK TimerManager::TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, + DWORD dwTime) { + timers.OnTick(); +} + +} // namespace taiga \ No newline at end of file diff --git a/src/taiga/timer.h b/src/taiga/timer.h new file mode 100644 index 000000000..97072c5f5 --- /dev/null +++ b/src/taiga/timer.h @@ -0,0 +1,63 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TAIGA_TIMER_H +#define TAIGA_TAIGA_TIMER_H + +#include "base/timer.h" + +namespace taiga { + +enum TimerIds { + kTimerHistory = 1, + kTimerLibrary, + kTimerMedia, + kTimerMemory, + kTimerStats, + kTimerTorrents +}; + +class Timer : public base::Timer { +public: + Timer(unsigned int id, int interval = 1 /*second*/, bool repeat = true); + ~Timer() {} + +protected: + void OnTimeout(); +}; + +class TimerManager : public base::TimerManager { +public: + void Initialize(); + + void UpdateEnabledState(); + void UpdateIntervalsFromSettings(); + void UpdateUi(); + +protected: + void OnTick(); + +private: + static void CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); +}; + +extern TimerManager timers; + +} // namespace taiga + +#endif // TAIGA_TAIGA_TIMER_H \ No newline at end of file diff --git a/src/taiga/update.cpp b/src/taiga/update.cpp new file mode 100644 index 000000000..c3bffd812 --- /dev/null +++ b/src/taiga/update.cpp @@ -0,0 +1,167 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include + +#include "base/file.h" +#include "base/foreach.h" +#include "base/string.h" +#include "base/url.h" +#include "base/xml.h" +#include "sync/service.h" +#include "taiga/http.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" +#include "taiga/update.h" +#include "ui/dlg/dlg_main.h" +#include "ui/ui.h" + +namespace taiga { + +UpdateHelper::UpdateHelper() + : restart_required_(false), + update_available_(false) { +} + +void UpdateHelper::Cancel() { + ConnectionManager.CancelRequest(client_uid_); +} + +bool UpdateHelper::Check() { + bool is_prerelease = !Taiga.version.prerelease_identifiers.empty(); + + HttpRequest http_request; + http_request.url.host = L"taiga.erengy.com"; + http_request.url.path = L"/update.php"; + http_request.url.query[L"channel"] = is_prerelease ? L"beta" : L"stable"; + http_request.url.query[L"check"] = ui::DlgMain.IsWindow() ? L"manual" : L"auto"; + http_request.url.query[L"version"] = std::wstring(Taiga.version); + http_request.url.query[L"service"] = GetCurrentService()->canonical_name(); + http_request.url.query[L"username"] = GetCurrentUsername(); + + client_uid_ = http_request.uid; + + ConnectionManager.MakeRequest(http_request, taiga::kHttpTaigaUpdateCheck); + return true; +} + +bool UpdateHelper::ParseData(std::wstring data) { + items.clear(); + download_path_.clear(); + latest_guid_.clear(); + restart_required_ = false; + update_available_ = false; + + xml_document document; + xml_parse_result parse_result = document.load(data.c_str()); + + if (parse_result.status != pugi::status_ok) + return false; + + xml_node channel = document.child(L"rss").child(L"channel"); + foreach_xmlnode_(item, channel, L"item") { + items.resize(items.size() + 1); + items.back().guid = XmlReadStrValue(item, L"guid"); + items.back().category = XmlReadStrValue(item, L"category"); + items.back().link = XmlReadStrValue(item, L"link"); + items.back().description = XmlReadStrValue(item, L"description"); + items.back().pub_date = XmlReadStrValue(item, L"pubDate"); + } + + auto current_version = Taiga.version; + auto latest_version = current_version; + foreach_(item, items) { + base::SemanticVersion item_version(item->guid); + if (item_version > latest_version) { + latest_guid_ = item->guid; + latest_version = item_version; + } + } + + if (latest_version > current_version) + update_available_ = true; + + return true; +} + +bool UpdateHelper::IsRestartRequired() const { + return restart_required_; +} + +bool UpdateHelper::IsUpdateAvailable() const { + return update_available_; +} + +bool UpdateHelper::IsDownloadAllowed() const { + if (IsUpdateAvailable()) { + ui::OnUpdateAvailable(); + return true; + } else { + ui::OnUpdateNotAvailable(); + return false; + } +} + +bool UpdateHelper::Download() { + auto feed_item = FindItem(latest_guid_); + if (!feed_item) + return false; + + download_path_ = AddTrailingSlash(GetPathOnly(Taiga.GetModulePath())); + download_path_ += GetFileName(feed_item->link); + + HttpRequest http_request; + http_request.url = feed_item->link; + + client_uid_ = http_request.uid; + + auto& client = ConnectionManager.GetClient(http_request); + client.set_download_path(download_path_); + ConnectionManager.MakeRequest(client, http_request, + taiga::kHttpTaigaUpdateDownload); + return true; +} + +bool UpdateHelper::RunInstaller() { + auto feed_item = FindItem(latest_guid_); + if (!feed_item) + return false; + + // /S runs the installer silently, /D overrides the default installation + // directory. Do not rely on the current directory here, as it isn't + // guaranteed to be the same as the module path. + std::wstring parameters = L"/S /D=" + GetPathOnly(Taiga.GetModulePath()); + + restart_required_ = Execute(download_path_, parameters); + + return restart_required_; +} + +void UpdateHelper::SetDownloadPath(const std::wstring& path) { + download_path_ = path; +} + +const GenericFeedItem* UpdateHelper::FindItem(const std::wstring& guid) const { + foreach_(item, items) + if (IsEqual(item->guid, latest_guid_)) + return &(*item); + + return nullptr; +} + +} // namespace taiga \ No newline at end of file diff --git a/update.h b/src/taiga/update.h similarity index 57% rename from update.h rename to src/taiga/update.h index 2176ce0c0..46ec757fc 100644 --- a/update.h +++ b/src/taiga/update.h @@ -1,56 +1,58 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef UPDATE_H -#define UPDATE_H - -#include "std.h" -#include "feed.h" -#include "http.h" - -// ============================================================================= - -class UpdateHelper { -public: - UpdateHelper(); - virtual ~UpdateHelper() {} - - bool Check(win32::App& app); - bool Download(); - bool IsDownloadAllowed() const; - bool IsRestartRequired() const; - bool IsUpdateAvailable() const; - bool ParseData(wstring data); - bool RunInstaller(); - void SetDownloadPath(const wstring& path); - - const GenericFeedItem* FindItem(const wstring& guid) const; - unsigned long long GetVersionValue(int major, int minor, int revision) const; - - HttpClient client; - vector items; - -private: - win32::App* app_; - wstring download_path_; - wstring latest_guid_; - bool restart_required_; - bool update_available_; -}; - -#endif // UPDATE_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TAIGA_UPDATE_H +#define TAIGA_TAIGA_UPDATE_H + +#include +#include + +#include "track/feed.h" + +namespace taiga { + +class UpdateHelper { +public: + UpdateHelper(); + virtual ~UpdateHelper() {} + + void Cancel(); + bool Check(); + bool Download(); + bool IsDownloadAllowed() const; + bool IsRestartRequired() const; + bool IsUpdateAvailable() const; + bool ParseData(std::wstring data); + bool RunInstaller(); + void SetDownloadPath(const std::wstring& path); + + std::vector items; + +private: + const GenericFeedItem* FindItem(const std::wstring& guid) const; + + std::wstring download_path_; + std::wstring latest_guid_; + bool restart_required_; + bool update_available_; + std::wstring client_uid_; +}; + +} // namespace taiga + +#endif // TAIGA_TAIGA_UPDATE_H diff --git a/version.h b/src/taiga/version.h similarity index 57% rename from version.h rename to src/taiga/version.h index 3c5e60f18..849eb6ad6 100644 --- a/version.h +++ b/src/taiga/version.h @@ -1,33 +1,34 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef VERSION_H -#define VERSION_H - -#define XSTRINGIFY(s) STRINGIFY(s) -#define STRINGIFY(s) #s - -#define VERSION_MAJOR 1 -#define VERSION_MINOR 0 -#define VERSION_REVISION 282 -#define VERSION_BUILD 0 - -#define VERSION_VALUE_DIGITAL VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD -#define VERSION_VALUE_STRING XSTRINGIFY(VERSION_MAJOR) "." XSTRINGIFY(VERSION_MINOR) "." XSTRINGIFY(VERSION_REVISION) "\0" - -#endif // VERSION_H +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TAIGA_VERSION_H +#define TAIGA_TAIGA_VERSION_H + +#define XSTRINGIFY(s) STRINGIFY(s) +#define STRINGIFY(s) #s + +#define TAIGA_VERSION_MAJOR 1 +#define TAIGA_VERSION_MINOR 1 +#define TAIGA_VERSION_PATCH 0 +#define TAIGA_VERSION_PRE L"" +#define TAIGA_VERSION_BUILD 0 + +#define VERSION_VALUE_DIGITAL TAIGA_VERSION_MAJOR, TAIGA_VERSION_MINOR, TAIGA_VERSION_PATCH, TAIGA_VERSION_BUILD +#define VERSION_VALUE_STRING XSTRINGIFY(TAIGA_VERSION_MAJOR) "." XSTRINGIFY(TAIGA_VERSION_MINOR) "." XSTRINGIFY(TAIGA_VERSION_PATCH) "\0" + +#endif // TAIGA_TAIGA_VERSION_H diff --git a/version.rc b/src/taiga/version.rc similarity index 91% rename from version.rc rename to src/taiga/version.rc index 274cfc8e8..39749a2a9 100644 --- a/version.rc +++ b/src/taiga/version.rc @@ -1,44 +1,43 @@ -// Generated by ResEdit 1.5.10 -// Copyright (C) 2006-2012 -// http://www.resedit.net - -#include -#include -#include -#include "version.h" - - - - -// -// Version Information resources -// -LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL -1 VERSIONINFO - FILEVERSION VERSION_VALUE_DIGITAL - PRODUCTVERSION VERSION_VALUE_DIGITAL - FILEOS VOS_NT_WINDOWS32 - FILETYPE VFT_APP - FILESUBTYPE VFT2_UNKNOWN - FILEFLAGSMASK 0x00000000 - FILEFLAGS 0x00000000 -{ - BLOCK "StringFileInfo" - { - BLOCK "0409FDE9" - { - VALUE "Comments", "MyAnimeList client" - VALUE "CompanyName", "erengy" - VALUE "FileDescription", "Taiga" - VALUE "FileVersion", VERSION_VALUE_STRING - VALUE "InternalName", "Taiga" - VALUE "OriginalFilename", "Taiga.exe" - VALUE "ProductName", "Taiga" - VALUE "ProductVersion", VERSION_VALUE_STRING - } - } - BLOCK "VarFileInfo" - { - VALUE "Translation", 0x0409, 0xFDE9 - } -} +// Generated by ResEdit 1.5.10 +// Copyright (C) 2006-2012 +// http://www.resedit.net + +#include +#include +#include +#include "version.h" + + + + +// +// Version Information resources +// +LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL +1 VERSIONINFO + FILEVERSION VERSION_VALUE_DIGITAL + PRODUCTVERSION VERSION_VALUE_DIGITAL + FILEOS VOS_NT_WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE VFT2_UNKNOWN + FILEFLAGSMASK 0x00000000 + FILEFLAGS 0x00000000 +{ + BLOCK "StringFileInfo" + { + BLOCK "0409FDE9" + { + VALUE "CompanyName", "erengy" + VALUE "FileDescription", "Taiga" + VALUE "FileVersion", VERSION_VALUE_STRING + VALUE "InternalName", "Taiga" + VALUE "OriginalFilename", "Taiga.exe" + VALUE "ProductName", "Taiga" + VALUE "ProductVersion", VERSION_VALUE_STRING + } + } + BLOCK "VarFileInfo" + { + VALUE "Translation", 0x0409, 0xFDE9 + } +} diff --git a/src/track/feed.cpp b/src/track/feed.cpp new file mode 100644 index 000000000..0285b13ed --- /dev/null +++ b/src/track/feed.cpp @@ -0,0 +1,492 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include + +#include "base/base64.h" +#include "base/file.h" +#include "base/foreach.h" +#include "base/html.h" +#include "base/log.h" +#include "base/string.h" +#include "base/url.h" +#include "base/xml.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "taiga/http.h" +#include "taiga/path.h" +#include "taiga/settings.h" +#include "track/feed.h" +#include "track/recognition.h" +#include "ui/dialog.h" +#include "ui/ui.h" + +class Aggregator Aggregator; + +GenericFeedItem::GenericFeedItem() + : permalink(true) { +} + +FeedItem::FeedItem() + : index(-1), + state(kFeedItemBlank) { +} + +void FeedItem::Discard(int option) { + switch (option) { + default: + case kFeedFilterOptionDefault: + state = kFeedItemDiscardedNormal; + break; + case kFeedFilterOptionDeactivate: + state = kFeedItemDiscardedInactive; + break; + case kFeedFilterOptionHide: + state = kFeedItemDiscardedHidden; + break; + } +} + +bool FeedItem::IsDiscarded() const { + switch (state) { + case kFeedItemDiscardedNormal: + case kFeedItemDiscardedInactive: + case kFeedItemDiscardedHidden: + return true; + default: + return false; + } +} + +bool FeedItem::operator<(const FeedItem& item) const { + // Initialize priority list + static const int state_priorities[] = {1, 2, 3, 4, 0}; + + // Sort items by the priority of their state + return state_priorities[this->state] < state_priorities[item.state]; +} + +//////////////////////////////////////////////////////////////////////////////// + +Feed::Feed() + : category(kFeedCategoryLink), + download_index(-1) { +} + +bool Feed::Check(const std::wstring& source, bool automatic) { + if (source.empty()) + return false; + + link = source; + + switch (category) { + case kFeedCategoryLink: + ui::EnableDialogInput(ui::kDialogTorrents, false); + break; + } + + HttpRequest http_request; + http_request.url = link; + http_request.parameter = reinterpret_cast(this); + + auto client_mode = automatic ? + taiga::kHttpFeedCheckAuto : taiga::kHttpFeedCheck; + auto& client = ConnectionManager.GetClient(http_request); + client.set_download_path(GetDataPath() + L"feed.xml"); + ConnectionManager.MakeRequest(client, http_request, client_mode); + + return true; +} + +bool Feed::Download(int index) { + if (category != kFeedCategoryLink) + return false; + + auto client_mode = taiga::kHttpFeedDownload; + if (index == -1) { + for (size_t i = 0; i < items.size(); i++) { + if (items.at(i).state == kFeedItemSelected) { + client_mode = taiga::kHttpFeedDownloadAll; + index = i; + break; + } + } + } + if (index < 0 || index > static_cast(items.size())) + return false; + download_index = index; + + ui::ChangeStatusText(L"Downloading \"" + items[index].title + L"\"..."); + ui::EnableDialogInput(ui::kDialogTorrents, false); + + std::wstring file = items[index].title + L".torrent"; + ValidateFileName(file); + file = GetDataPath() + file; + + HttpRequest http_request; + http_request.url = items[index].link; + http_request.parameter = reinterpret_cast(this); + + auto& client = ConnectionManager.GetClient(http_request); + client.set_download_path(file); + ConnectionManager.MakeRequest(client, http_request, client_mode); + + return true; +} + +bool Feed::ExamineData() { + foreach_(it, items) { + // Examine title and compare with anime list items + Meow.ExamineTitle(it->title, it->episode_data, + true, true, true, true, false); + Meow.MatchDatabase(it->episode_data, true, true); + + // Update last aired episode number + if (it->episode_data.anime_id > anime::ID_UNKNOWN) { + auto anime_item = AnimeDatabase.FindItem(it->episode_data.anime_id); + int episode_number = anime::GetEpisodeHigh(it->episode_data.number); + anime_item->SetLastAiredEpisodeNumber(episode_number); + } + } + + Aggregator.filter_manager.MarkNewEpisodes(*this); + // Preferences have lower priority, so we need to handle other filters + // first in order to avoid discarding items that we actually want. + Aggregator.filter_manager.Filter(*this, false); + Aggregator.filter_manager.Filter(*this, true); + // Archived items must be discarded after other filters are processed. + Aggregator.filter_manager.FilterArchived(*this); + + // Sort items + std::stable_sort(items.begin(), items.end()); + // Re-assign item indexes + for (size_t i = 0; i < items.size(); i++) + items.at(i).index = i; + + return Aggregator.filter_manager.IsItemDownloadAvailable(*this); +} + +std::wstring Feed::GetDataPath() { + std::wstring path = taiga::GetPath(taiga::kPathFeed); + + if (!link.empty()) { + Url url(link); + path += Base64Encode(url.host, true) + L"\\"; + } + + return path; +} + +bool Feed::Load() { + std::wstring file = GetDataPath() + L"feed.xml"; + items.clear(); + + xml_document document; + xml_parse_result parse_result = document.load_file(file.c_str()); + + if (parse_result.status != pugi::status_ok) + return false; + + // Read channel information + xml_node channel = document.child(L"rss").child(L"channel"); + title = XmlReadStrValue(channel, L"title"); + link = XmlReadStrValue(channel, L"link"); + description = XmlReadStrValue(channel, L"description"); + + // Read items + foreach_xmlnode_(item, channel, L"item") { + // Read data + items.resize(items.size() + 1); + items.back().index = items.size() - 1; + items.back().category = XmlReadStrValue(item, L"category"); + items.back().title = XmlReadStrValue(item, L"title"); + items.back().link = XmlReadStrValue(item, L"link"); + items.back().description = XmlReadStrValue(item, L"description"); + + // Remove if title or link is empty + if (category == kFeedCategoryLink) { + if (items.back().title.empty() || items.back().link.empty()) { + items.pop_back(); + continue; + } + } + + // Clean up title + DecodeHtmlEntities(items.back().title); + Replace(items.back().title, L"\\'", L"'"); + // Clean up description + Replace(items.back().description, L"
", L"\n"); + Replace(items.back().description, L"
", L"\n"); + StripHtmlTags(items.back().description); + DecodeHtmlEntities(items.back().description); + Trim(items.back().description, L" \n"); + Aggregator.ParseDescription(items.back(), link); + Replace(items.back().description, L"\n", L" | "); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +Aggregator::Aggregator() { + // Add torrent feed + feeds.resize(feeds.size() + 1); + feeds.back().category = kFeedCategoryLink; +} + +Feed* Aggregator::Get(FeedCategory category) { + foreach_(it, feeds) + if (it->category == category) + return &(*it); + + return nullptr; +} + +bool Aggregator::Notify(const Feed& feed) { + return ui::OnFeedNotify(feed); +} + +bool Aggregator::SearchArchive(const std::wstring& file) { + foreach_(it, file_archive) + if (*it == file) + return true; + + return false; +} + +void Aggregator::HandleFeedCheck(Feed& feed, bool automatic) { + feed.Load(); + + bool success = feed.ExamineData(); + ui::OnFeedCheck(success); + + if (automatic) { + switch (Settings.GetInt(taiga::kTorrent_Discovery_NewAction)) { + case 1: // Notify + Notify(feed); + break; + case 2: // Download + feed.Download(-1); + break; + } + } +} + +void Aggregator::HandleFeedDownload(Feed& feed, bool download_all) { + auto feed_item = reinterpret_cast(&feed.items.at(feed.download_index)); + + file_archive.push_back(feed_item->title); + + std::wstring file = feed_item->title; + ValidateFileName(file); + file = feed.GetDataPath() + file + L".torrent"; + + if (FileExists(file)) { + std::wstring app_path; + std::wstring parameters; + + switch (Settings.GetInt(taiga::kTorrent_Download_AppMode)) { + case 1: // Default application + app_path = GetDefaultAppPath(L".torrent", L""); + break; + case 2: // Custom application + app_path = Settings[taiga::kTorrent_Download_AppPath]; + break; + } + + if (Settings.GetBool(taiga::kTorrent_Download_UseAnimeFolder) && + InStr(app_path, L"utorrent", 0, true) > -1) { + std::wstring download_path; + // Use anime folder as the download folder + auto anime_id = feed_item->episode_data.anime_id; + auto anime_item = AnimeDatabase.FindItem(anime_id); + if (anime_item) { + std::wstring anime_folder = anime_item->GetFolder(); + if (!anime_folder.empty() && FolderExists(anime_folder)) + download_path = anime_folder; + } + // If no anime folder is set, use an alternative folder + if (download_path.empty()) { + if (Settings.GetBool(taiga::kTorrent_Download_FallbackOnFolder) && + !Settings[taiga::kTorrent_Download_Location].empty()) { + download_path = Settings[taiga::kTorrent_Download_Location]; + } + // Create a subfolder using the anime title as its name + if (!download_path.empty() && + Settings.GetBool(taiga::kTorrent_Download_CreateSubfolder)) { + std::wstring anime_title; + if (anime_item) { + anime_title = anime_item->GetTitle(); + } else { + anime_title = feed_item->episode_data.title; + } + ValidateFileName(anime_title); + TrimRight(anime_title, L"."); + AddTrailingSlash(download_path); + download_path += anime_title; + if (!CreateFolder(download_path)) + LOG(LevelWarning, L"Subfolder could not be created."); + if (anime_item) { + anime_item->SetFolder(download_path); + Settings.Save(); + } + } + } + + // Set the command line parameter + if (!download_path.empty()) + parameters = L"/directory \"" + download_path + L"\" "; + } + + parameters += L"\"" + file + L"\""; + Execute(app_path, parameters); + + feed_item->state = kFeedItemDiscardedNormal; + ui::OnFeedDownload(true); + } + + feed.download_index = -1; + + if (download_all) + if (feed.Download(-1)) + return; + + ui::OnFeedDownload(false); +} + +void Aggregator::ParseDescription(FeedItem& feed_item, + const std::wstring& source) { + // AnimeSuki + if (InStr(source, L"animesuki", 0, true) > -1) { + std::wstring size_str = L"Filesize: "; + std::vector description_vector; + Split(feed_item.description, L"\n", description_vector); + if (description_vector.size() > 2) { + feed_item.episode_data.file_size = + description_vector[2].substr(size_str.length()); + } + if (description_vector.size() > 1) { + feed_item.description = + description_vector[0] + L" " + description_vector[1]; + return; + } + feed_item.description.clear(); + + // Baka-Updates + } else if (InStr(source, L"baka-updates", 0, true) > -1) { + int index_begin = InStr(feed_item.description, L"Released on"); + int index_end = feed_item.description.length(); + if (index_begin > -1) + index_end -= index_begin; + if (index_begin == -1) + index_begin = 0; + feed_item.description = + feed_item.description.substr(index_begin, index_end); + + // NyaaTorrents + } else if (InStr(source, L"nyaa", 0, true) > -1) { + feed_item.episode_data.file_size = + InStr(feed_item.description, L" - ", L" - "); + Erase(feed_item.description, feed_item.episode_data.file_size); + Replace(feed_item.description, L"- -", L"-"); + + // TokyoTosho + } else if (InStr(source, L"tokyotosho", 0, true) > -1) { + std::wstring size_str = L"Size: "; + std::wstring comment_str = L"Comment: "; + std::vector description_vector; + Split(feed_item.description, L"\n", description_vector); + feed_item.description.clear(); + foreach_(it, description_vector) { + if (StartsWith(*it, size_str)) { + feed_item.episode_data.file_size = it->substr(size_str.length()); + } else if (StartsWith(*it, comment_str)) { + feed_item.description = it->substr(comment_str.length()); + } else if (InStr(*it, L"magnet:?") > -1) { + feed_item.magnet_link = L"magnet:?" + + InStr(*it, L"Magnet Link"); + } + } + + // Yahoo! Pipes + } else if (InStr(source, L"pipes.yahoo.com", 0, true) > -1) { + Erase(feed_item.title, L" "); + } +} + +bool Aggregator::LoadArchive() { + xml_document document; + std::wstring path = taiga::GetPath(taiga::kPathFeedHistory); + xml_parse_result parse_result = document.load_file(path.c_str()); + + if (parse_result.status != pugi::status_ok) + return false; + + // Read discarded + file_archive.clear(); + xml_node archive_node = document.child(L"archive"); + foreach_xmlnode_(node, archive_node, L"item") { + file_archive.push_back(node.attribute(L"title").value()); + } + + return true; +} + +bool Aggregator::SaveArchive() { + xml_document document; + xml_node archive_node = document.append_child(L"archive"); + + size_t max_count = Settings.GetInt(taiga::kTorrent_Filter_ArchiveMaxCount); + + if (max_count > 0) { + size_t length = file_archive.size(); + size_t i = 0; + if (length > max_count) + i = length - max_count; + for ( ; i < file_archive.size(); i++) { + xml_node xml_item = archive_node.append_child(L"item"); + xml_item.append_attribute(L"title") = file_archive[i].c_str(); + } + } + + std::wstring path = taiga::GetPath(taiga::kPathFeedHistory); + return XmlWriteDocumentToFile(document, path); +} + +bool Aggregator::CompareFeedItems(const GenericFeedItem& item1, + const GenericFeedItem& item2) { + // Check for guid element first + if (item1.permalink && item2.permalink) + if (!item1.guid.empty() || !item2.guid.empty()) + if (item1.guid == item2.guid) + return true; + + // Fallback to link element + if (!item1.link.empty() || !item2.link.empty()) + if (item1.link == item2.link) + return true; + + // Fallback to title element + if (!item1.title.empty() || !item2.title.empty()) + if (item1.title == item2.title) + return true; + + // Items are different + return false; +} \ No newline at end of file diff --git a/src/track/feed.h b/src/track/feed.h new file mode 100644 index 000000000..7b987f898 --- /dev/null +++ b/src/track/feed.h @@ -0,0 +1,154 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TRACK_FEED_H +#define TAIGA_TRACK_FEED_H + +#include +#include + +#include "library/anime_episode.h" +#include "track/feed_filter.h" + +enum FeedItemState { + kFeedItemBlank, + kFeedItemDiscardedNormal, + kFeedItemDiscardedInactive, + kFeedItemDiscardedHidden, + kFeedItemSelected +}; + +enum FeedCategory { + // Broadcatching for torrent files and DDL + kFeedCategoryLink, + // News around the web + kFeedCategoryText, + // Airing times for anime titles + kFeedCategoryTime +}; + +enum TorrentCategory { + kTorrentCategoryAnime, + kTorrentCategoryBatch, + kTorrentCategoryOther +}; + +//////////////////////////////////////////////////////////////////////////////// + +class GenericFeedItem { +public: + GenericFeedItem(); + virtual ~GenericFeedItem() {} + + std::wstring title, + link, + description, + author, + category, + comments, + enclosure, + guid, + pub_date, + source; + + bool permalink; +}; + +class FeedItem : public GenericFeedItem { +public: + FeedItem(); + ~FeedItem() {}; + + void Discard(int option); + bool IsDiscarded() const; + + bool operator<(const FeedItem& item) const; + + int index; + std::wstring magnet_link; + FeedItemState state; + + class EpisodeData : public anime::Episode { + public: + EpisodeData() : new_episode(false) {} + std::wstring file_size; + bool new_episode; + } episode_data; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class GenericFeed { +public: + // Required channel elements + std::wstring title, + link, + description; + + // Optional channel elements are not implemented. + // See http://www.rssboard.org/rss-specification for more information. + + // Feed items + std::vector items; +}; + +class Feed : public GenericFeed { +public: + Feed(); + ~Feed() {} + + bool Check(const std::wstring& source, bool automatic = false); + bool Download(int index); + bool ExamineData(); + std::wstring GetDataPath(); + bool Load(); + + FeedCategory category; + int download_index; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class Aggregator { +public: + Aggregator(); + ~Aggregator() {} + + Feed* Get(FeedCategory category); + + void HandleFeedCheck(Feed& feed, bool automatic); + void HandleFeedDownload(Feed& feed, bool download_all); + + bool Notify(const Feed& feed); + void ParseDescription(FeedItem& feed_item, const std::wstring& source); + + bool LoadArchive(); + bool SaveArchive(); + bool SearchArchive(const std::wstring& file); + + std::vector feeds; + std::vector file_archive; + FeedFilterManager filter_manager; + +private: + bool CompareFeedItems(const GenericFeedItem& item1, const GenericFeedItem& item2); +}; + +extern Aggregator Aggregator; + +#endif // TAIGA_TRACK_FEED_H \ No newline at end of file diff --git a/src/track/feed_filter.cpp b/src/track/feed_filter.cpp new file mode 100644 index 000000000..b3a1cb1a2 --- /dev/null +++ b/src/track/feed_filter.cpp @@ -0,0 +1,820 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "base/log.h" +#include "base/string.h" +#include "library/anime.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "taiga/script.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" +#include "track/feed.h" +#include "track/feed_filter.h" + +bool EvaluateCondition(const FeedFilterCondition& condition, + const FeedItem& item) { + bool is_numeric = false; + std::wstring element; + std::wstring value = ReplaceVariables(condition.value, item.episode_data); + auto anime = AnimeDatabase.FindItem(item.episode_data.anime_id); + + switch (condition.element) { + case kFeedFilterElement_File_Title: + element = item.title; + break; + case kFeedFilterElement_File_Category: + element = item.category; + break; + case kFeedFilterElement_File_Description: + element = item.description; + break; + case kFeedFilterElement_File_Link: + element = item.link; + break; + case kFeedFilterElement_Meta_Id: + if (anime) + element = ToWstr(anime->GetId()); + is_numeric = true; + break; + case kFeedFilterElement_Episode_Title: + element = item.episode_data.title; + break; + case kFeedFilterElement_Meta_DateStart: + if (anime) + element = anime->GetDateStart(); + break; + case kFeedFilterElement_Meta_DateEnd: + if (anime) + element = anime->GetDateEnd(); + break; + case kFeedFilterElement_Meta_Episodes: + if (anime) + element = ToWstr(anime->GetEpisodeCount()); + is_numeric = true; + break; + case kFeedFilterElement_Meta_Status: + if (anime) + element = ToWstr(anime->GetAiringStatus()); + is_numeric = true; + break; + case kFeedFilterElement_Meta_Type: + if (anime) + element = ToWstr(anime->GetType()); + is_numeric = true; + break; + case kFeedFilterElement_User_Status: + if (anime) + element = ToWstr(anime->GetMyStatus()); + is_numeric = true; + break; + case kFeedFilterElement_Episode_Number: + element = ToWstr(anime::GetEpisodeHigh(item.episode_data.number)); + is_numeric = true; + break; + case kFeedFilterElement_Episode_Version: + element = item.episode_data.version; + if (element.empty()) + element = L"1"; + is_numeric = true; + break; + case kFeedFilterElement_Local_EpisodeAvailable: + if (anime) + element = ToWstr(anime->IsEpisodeAvailable( + anime::GetEpisodeHigh(item.episode_data.number))); + is_numeric = true; + break; + case kFeedFilterElement_Episode_Group: + element = item.episode_data.group; + break; + case kFeedFilterElement_Episode_VideoResolution: + element = item.episode_data.resolution; + break; + case kFeedFilterElement_Episode_VideoType: + element = item.episode_data.video_type; + break; + } + + switch (condition.op) { + case kFeedFilterOperator_Equals: + if (is_numeric) { + if (IsEqual(value, L"True")) + return ToInt(element) == TRUE; + return ToInt(element) == ToInt(value); + } else { + if (condition.element == kFeedFilterElement_Episode_VideoResolution) { + return anime::TranslateResolution(element) == anime::TranslateResolution(condition.value); + } else { + return IsEqual(element, value); + } + } + case kFeedFilterOperator_NotEquals: + if (is_numeric) { + if (IsEqual(value, L"True")) + return ToInt(element) == TRUE; + return ToInt(element) != ToInt(value); + } else { + if (condition.element == kFeedFilterElement_Episode_VideoResolution) { + return anime::TranslateResolution(element) != anime::TranslateResolution(condition.value); + } else { + return !IsEqual(element, value); + } + } + case kFeedFilterOperator_IsGreaterThan: + if (is_numeric) { + return ToInt(element) > ToInt(value); + } else { + if (condition.element == kFeedFilterElement_Episode_VideoResolution) { + return anime::TranslateResolution(element) > anime::TranslateResolution(condition.value); + } else { + return CompareStrings(element, condition.value) > 0; + } + } + case kFeedFilterOperator_IsGreaterThanOrEqualTo: + if (is_numeric) { + return ToInt(element) >= ToInt(value); + } else { + if (condition.element == kFeedFilterElement_Episode_VideoResolution) { + return anime::TranslateResolution(element) >= anime::TranslateResolution(condition.value); + } else { + return CompareStrings(element, condition.value) >= 0; + } + } + case kFeedFilterOperator_IsLessThan: + if (is_numeric) { + return ToInt(element) < ToInt(value); + } else { + if (condition.element == kFeedFilterElement_Episode_VideoResolution) { + return anime::TranslateResolution(element) < anime::TranslateResolution(condition.value); + } else { + return CompareStrings(element, condition.value) < 0; + } + } + case kFeedFilterOperator_IsLessThanOrEqualTo: + if (is_numeric) { + return ToInt(element) <= ToInt(value); + } else { + if (condition.element == kFeedFilterElement_Episode_VideoResolution) { + return anime::TranslateResolution(element) <= anime::TranslateResolution(condition.value); + } else { + return CompareStrings(element, condition.value) <= 0; + } + } + case kFeedFilterOperator_BeginsWith: + return StartsWith(element, value); + case kFeedFilterOperator_EndsWith: + return EndsWith(element, value); + case kFeedFilterOperator_Contains: + return InStr(element, value, 0, true) > -1; + case kFeedFilterOperator_NotContains: + return InStr(element, value, 0, true) == -1; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + +FeedFilterCondition::FeedFilterCondition() + : element(kFeedFilterElement_Meta_Id), + op(kFeedFilterOperator_Equals) { +} + +FeedFilterCondition& FeedFilterCondition::operator=(const FeedFilterCondition& condition) { + element = condition.element; + op = condition.op; + value = condition.value; + + return *this; +} + +void FeedFilterCondition::Reset() { + element = kFeedFilterElement_File_Title; + op = kFeedFilterOperator_Equals; + value.clear(); +} + +//////////////////////////////////////////////////////////////////////////////// + +FeedFilter::FeedFilter() + : action(kFeedFilterActionDiscard), + enabled(true), + match(kFeedFilterMatchAll), + option(kFeedFilterOptionDefault) { +} + +FeedFilter& FeedFilter::operator=(const FeedFilter& filter) { + action = filter.action; + enabled = filter.enabled; + match = filter.match; + name = filter.name; + option = filter.option; + + conditions.resize(filter.conditions.size()); + std::copy(filter.conditions.begin(), filter.conditions.end(), + conditions.begin()); + + anime_ids.resize(filter.anime_ids.size()); + std::copy(filter.anime_ids.begin(), filter.anime_ids.end(), + anime_ids.begin()); + + return *this; +} + +void FeedFilter::AddCondition(FeedFilterElement element, + FeedFilterOperator op, + const std::wstring& value) { + conditions.resize(conditions.size() + 1); + conditions.back().element = element; + conditions.back().op = op; + conditions.back().value = value; +} + +void FeedFilter::Filter(Feed& feed, FeedItem& item, bool recursive) { + if (!enabled) + return; + + // No need to filter if the item was discarded before + if (item.IsDiscarded()) + return; + + if (!anime_ids.empty()) { + bool apply_filter = false; + foreach_(id, anime_ids) { + if (*id == item.episode_data.anime_id) { + apply_filter = true; + break; + } + } + if (!apply_filter) + return; // Filter doesn't apply to this item + } + + bool matched = false; + size_t condition_index = 0; + + switch (match) { + case kFeedFilterMatchAll: + matched = true; + for (size_t i = 0; i < conditions.size(); i++) { + if (!EvaluateCondition(conditions.at(i), item)) { + matched = false; + condition_index = i; + break; + } + } + break; + case kFeedFilterMatchAny: + matched = false; + for (size_t i = 0; i < conditions.size(); i++) { + if (EvaluateCondition(conditions.at(i), item)) { + matched = true; + condition_index = i; + break; + } + } + break; + } + + switch (action) { + case kFeedFilterActionDiscard: + if (matched) { + // Discard matched items, regardless of their previous state + item.Discard(option); + } else { + return; // Filter doesn't apply to this item + } + break; + + case kFeedFilterActionSelect: + if (matched) { + // Select matched items, if they were not discarded before + item.state = kFeedItemSelected; + } else { + return; // Filter doesn't apply to this item + } + break; + + case kFeedFilterActionPrefer: { + if (recursive) { + if (matched) { + foreach_(it, feed.items) { + // Do not bother if the item was discarded before + if (it->IsDiscarded()) + continue; + // Do not filter the same item again + if (it->index == item.index) + continue; + // Is it the same title? + if (it->episode_data.anime_id == anime::ID_NOTINLIST) { + if (!IsEqual(it->episode_data.title, item.episode_data.title)) + continue; + } else { + if (it->episode_data.anime_id != item.episode_data.anime_id) + continue; + } + // Is it the same episode? + if (it->episode_data.number != item.episode_data.number) + continue; + // Is it the same group? + if (!IsEqual(it->episode_data.group, item.episode_data.group)) + continue; + // Try applying the same filter + Filter(feed, *it, false); + } + } + // Filters are strong if they're limited, weak otherwise + bool strong_preference = !anime_ids.empty(); + if (strong_preference) { + if (matched) { + // Select matched items, if they were not discarded before + item.state = kFeedItemSelected; + } else { + // Discard mismatched items, regardless of their previous state + item.Discard(option); + } + } else { + return; // Filter doesn't apply to this item + } + } else { + // The fact that we're here means that the preference filter matched an + // item before, and now we're checking other items for mismatches. At + // this point, we don't care whether the preference is weak or strong. + if (!matched) { + // Discard mismatched items, regardless of their previous state + item.Discard(option); + } else { + return; // Filter doesn't apply to this item + } + } + break; + } + } + + if (Taiga.debug_mode) { + std::wstring filter_text = + (item.IsDiscarded() ? L"!FILTER :: " : L"FILTER :: ") + + Aggregator.filter_manager.TranslateConditions(*this, condition_index); + item.description = filter_text + L" -- " + item.description; + } +} + +void FeedFilter::Reset() { + enabled = true; + action = kFeedFilterActionDiscard; + match = kFeedFilterMatchAll; + anime_ids.clear(); + conditions.clear(); + name.clear(); +} + +//////////////////////////////////////////////////////////////////////////////// + +FeedFilterPreset::FeedFilterPreset() + : is_default(false) { +} + +FeedFilterManager::FeedFilterManager() { + InitializePresets(); + InitializeShortcodes(); +} + +void FeedFilterManager::AddPresets() { + foreach_(preset, presets) { + if (!preset->is_default) + continue; + + AddFilter(preset->filter.action, preset->filter.match, + preset->filter.option, preset->filter.enabled, + preset->filter.name); + + foreach_(condition, preset->filter.conditions) { + filters.back().AddCondition(condition->element, + condition->op, + condition->value); + } + } +} + +void FeedFilterManager::AddFilter(FeedFilterAction action, + FeedFilterMatch match, + FeedFilterOption option, + bool enabled, + const std::wstring& name) { + filters.resize(filters.size() + 1); + filters.back().action = action; + filters.back().enabled = enabled; + filters.back().match = match; + filters.back().name = name; + filters.back().option = option; +} + +void FeedFilterManager::Cleanup() { + foreach_(filter, filters) { + foreach_(id, filter->anime_ids) { + if (!AnimeDatabase.FindItem(*id)) { + if (filter->anime_ids.size() > 1) { + id = filter->anime_ids.erase(id) - 1; + continue; + } else { + filter = filters.erase(filter) - 1; + break; + } + } + } + } +} + +void FeedFilterManager::Filter(Feed& feed, bool preferences) { + if (!Settings.GetBool(taiga::kTorrent_Filter_Enabled)) + return; + + foreach_(item, feed.items) { + foreach_(filter, filters) { + if (preferences != (filter->action == kFeedFilterActionPrefer)) + continue; + filter->Filter(feed, *item, true); + } + } +} + +void FeedFilterManager::FilterArchived(Feed& feed) { + foreach_(item, feed.items) { + if (!item->IsDiscarded()) { + bool found = Aggregator.SearchArchive(item->title); + if (found) { + item->state = kFeedItemDiscardedNormal; + if (Taiga.debug_mode) { + std::wstring filter_text = L"!FILTER :: Archived"; + item->description = filter_text + L" -- " + item->description; + } + } + } + } +} + +bool FeedFilterManager::IsItemDownloadAvailable(Feed& feed) { + foreach_c_(item, feed.items) + if (item->state == kFeedItemSelected) + return true; + + return false; +} + +void FeedFilterManager::MarkNewEpisodes(Feed& feed) { + foreach_(item, feed.items) { + auto anime_item = AnimeDatabase.FindItem(item->episode_data.anime_id); + if (anime_item) { + int number = anime::GetEpisodeHigh(item->episode_data.number); + if (number > anime_item->GetMyLastWatchedEpisode()) + item->episode_data.new_episode = true; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void FeedFilterManager::InitializePresets() { + #define ADD_PRESET(action_, match_, is_default_, option_, name_, description_) \ + presets.resize(presets.size() + 1); \ + presets.back().description = description_; \ + presets.back().is_default = is_default_; \ + presets.back().filter.action = action_; \ + presets.back().filter.enabled = true; \ + presets.back().filter.match = match_; \ + presets.back().filter.option = option_; \ + presets.back().filter.name = name_; + #define ADD_CONDITION(e, o, v) \ + presets.back().filter.AddCondition(e, o, v); + + /* Preset filters */ + + // Custom + ADD_PRESET(kFeedFilterActionDiscard, kFeedFilterMatchAll, false, kFeedFilterOptionDefault, + L"(Custom)", + L"Lets you create a custom filter from scratch"); + + // Fansub group + ADD_PRESET(kFeedFilterActionPrefer, kFeedFilterMatchAll, false, kFeedFilterOptionDefault, + L"[Fansub] Anime", + L"Lets you choose a fansub group for one or more anime"); + ADD_CONDITION(kFeedFilterElement_Episode_Group, kFeedFilterOperator_Equals, L"TaigaSubs (change this)"); + + // Discard bad video keywords + ADD_PRESET(kFeedFilterActionDiscard, kFeedFilterMatchAny, false, kFeedFilterOptionDefault, + L"Discard bad video keywords", + L"Discards everything that is AVI, DIVX, LQ, RMVB, SD, WMV or XVID"); + ADD_CONDITION(kFeedFilterElement_Episode_VideoType, kFeedFilterOperator_Contains, L"AVI"); + ADD_CONDITION(kFeedFilterElement_Episode_VideoType, kFeedFilterOperator_Contains, L"DIVX"); + ADD_CONDITION(kFeedFilterElement_Episode_VideoType, kFeedFilterOperator_Contains, L"LQ"); + ADD_CONDITION(kFeedFilterElement_Episode_VideoType, kFeedFilterOperator_Contains, L"RMVB"); + ADD_CONDITION(kFeedFilterElement_Episode_VideoType, kFeedFilterOperator_Contains, L"SD"); + ADD_CONDITION(kFeedFilterElement_Episode_VideoType, kFeedFilterOperator_Contains, L"WMV"); + ADD_CONDITION(kFeedFilterElement_Episode_VideoType, kFeedFilterOperator_Contains, L"XVID"); + + // Prefer new versions + ADD_PRESET(kFeedFilterActionPrefer, kFeedFilterMatchAny, false, kFeedFilterOptionDefault, + L"Prefer new versions", + L"Prefers v2 files and above when there are earlier releases of the same episode as well"); + ADD_CONDITION(kFeedFilterElement_Episode_Version, kFeedFilterOperator_IsGreaterThan, L"1"); + + /* Default filters */ + + // Select currently watching + ADD_PRESET(kFeedFilterActionSelect, kFeedFilterMatchAny, true, kFeedFilterOptionDefault, + L"Select currently watching", + L"Selects files that belong to anime that you're currently watching"); + ADD_CONDITION(kFeedFilterElement_User_Status, kFeedFilterOperator_Equals, ToWstr(anime::kWatching)); + + // Discard unknown titles + ADD_PRESET(kFeedFilterActionDiscard, kFeedFilterMatchAny, true, kFeedFilterOptionDeactivate, + L"Discard unknown titles", + L"Discards files that do not belong to any anime in your list"); + ADD_CONDITION(kFeedFilterElement_Meta_Id, kFeedFilterOperator_Equals, L""); + + // Discard watched and available episodes + ADD_PRESET(kFeedFilterActionDiscard, kFeedFilterMatchAny, true, kFeedFilterOptionDefault, + L"Discard watched and available episodes", + L"Discards episodes you've already watched or downloaded"); + ADD_CONDITION(kFeedFilterElement_Episode_Number, kFeedFilterOperator_IsLessThanOrEqualTo, L"%watched%"); + ADD_CONDITION(kFeedFilterElement_Local_EpisodeAvailable, kFeedFilterOperator_Equals, L"True"); + + // Prefer high-resolution files + ADD_PRESET(kFeedFilterActionPrefer, kFeedFilterMatchAny, true, kFeedFilterOptionDefault, + L"Prefer high-resolution files", + L"Prefers 720p files when there are other files of the same episode as well"); + ADD_CONDITION(kFeedFilterElement_Episode_VideoResolution, kFeedFilterOperator_Equals, L"720p"); + + #undef ADD_CONDITION + #undef ADD_PRESET +} + +void FeedFilterManager::InitializeShortcodes() { + action_shortcodes_[kFeedFilterActionDiscard] = L"discard"; + action_shortcodes_[kFeedFilterActionSelect] = L"select"; + action_shortcodes_[kFeedFilterActionPrefer] = L"prefer"; + + element_shortcodes_[kFeedFilterElement_Meta_Id] = L"meta_id"; + element_shortcodes_[kFeedFilterElement_Meta_Status] = L"meta_status"; + element_shortcodes_[kFeedFilterElement_Meta_Type] = L"meta_type"; + element_shortcodes_[kFeedFilterElement_Meta_Episodes] = L"meta_episodes"; + element_shortcodes_[kFeedFilterElement_Meta_DateStart] = L"meta_date_start"; + element_shortcodes_[kFeedFilterElement_Meta_DateEnd] = L"meta_date_end"; + element_shortcodes_[kFeedFilterElement_User_Status] = L"user_status"; + element_shortcodes_[kFeedFilterElement_Local_EpisodeAvailable] = L"local_episode_available"; + element_shortcodes_[kFeedFilterElement_Episode_Title] = L"episode_title"; + element_shortcodes_[kFeedFilterElement_Episode_Number] = L"episode_number"; + element_shortcodes_[kFeedFilterElement_Episode_Version] = L"episode_version"; + element_shortcodes_[kFeedFilterElement_Episode_Group] = L"episode_group"; + element_shortcodes_[kFeedFilterElement_Episode_VideoResolution] = L"episode_video_resolution"; + element_shortcodes_[kFeedFilterElement_Episode_VideoType] = L"episode_video_type"; + element_shortcodes_[kFeedFilterElement_File_Title] = L"file_title"; + element_shortcodes_[kFeedFilterElement_File_Category] = L"file_category"; + element_shortcodes_[kFeedFilterElement_File_Description] = L"file_description"; + element_shortcodes_[kFeedFilterElement_File_Link] = L"file_link"; + + match_shortcodes_[kFeedFilterMatchAll] = L"all"; + match_shortcodes_[kFeedFilterMatchAny] = L"any"; + + operator_shortcodes_[kFeedFilterOperator_Equals] = L"equals"; + operator_shortcodes_[kFeedFilterOperator_NotEquals] = L"notequals"; + operator_shortcodes_[kFeedFilterOperator_IsGreaterThan] = L"gt"; + operator_shortcodes_[kFeedFilterOperator_IsGreaterThanOrEqualTo] = L"ge"; + operator_shortcodes_[kFeedFilterOperator_IsLessThan] = L"lt"; + operator_shortcodes_[kFeedFilterOperator_IsLessThanOrEqualTo] = L"le"; + operator_shortcodes_[kFeedFilterOperator_BeginsWith] = L"beginswith"; + operator_shortcodes_[kFeedFilterOperator_EndsWith] = L"endswith"; + operator_shortcodes_[kFeedFilterOperator_Contains] = L"contains"; + operator_shortcodes_[kFeedFilterOperator_NotContains] = L"notcontains"; + + option_shortcodes_[kFeedFilterOptionDefault] = L"default"; + option_shortcodes_[kFeedFilterOptionDeactivate] = L"deactivate"; + option_shortcodes_[kFeedFilterOptionHide] = L"hide"; +} + +std::wstring FeedFilterManager::CreateNameFromConditions( + const FeedFilter& filter) { + // TODO + return L"New Filter"; +} + +std::wstring FeedFilterManager::TranslateCondition( + const FeedFilterCondition& condition) { + return TranslateElement(condition.element) + L" " + + TranslateOperator(condition.op) + L" \"" + + TranslateValue(condition) + L"\""; +} + +std::wstring FeedFilterManager::TranslateConditions(const FeedFilter& filter, + size_t index) { + std::wstring str; + + size_t max_index = (filter.match == kFeedFilterMatchAll) ? + filter.conditions.size() : index + 1; + + for (size_t i = index; i < max_index; i++) { + if (i > index) + str += L" & "; + str += TranslateCondition(filter.conditions[i]); + } + + return str; +} + +std::wstring FeedFilterManager::TranslateElement(int element) { + switch (element) { + case kFeedFilterElement_File_Title: + return L"File name"; + case kFeedFilterElement_File_Category: + return L"File category"; + case kFeedFilterElement_File_Description: + return L"File description"; + case kFeedFilterElement_File_Link: + return L"File link"; + case kFeedFilterElement_Meta_Id: + return L"Anime ID"; + case kFeedFilterElement_Episode_Title: + return L"Episode title"; + case kFeedFilterElement_Meta_DateStart: + return L"Anime date started"; + case kFeedFilterElement_Meta_DateEnd: + return L"Anime date ended"; + case kFeedFilterElement_Meta_Episodes: + return L"Anime episode count"; + case kFeedFilterElement_Meta_Status: + return L"Anime airing status"; + case kFeedFilterElement_Meta_Type: + return L"Anime type"; + case kFeedFilterElement_User_Status: + return L"Anime watching status"; + case kFeedFilterElement_Episode_Number: + return L"Episode number"; + case kFeedFilterElement_Episode_Version: + return L"Episode version"; + case kFeedFilterElement_Local_EpisodeAvailable: + return L"Episode availability"; + case kFeedFilterElement_Episode_Group: + return L"Episode fansub group"; + case kFeedFilterElement_Episode_VideoResolution: + return L"Episode video resolution"; + case kFeedFilterElement_Episode_VideoType: + return L"Episode video type"; + default: + return L"?"; + } +} + +std::wstring FeedFilterManager::TranslateOperator(int op) { + switch (op) { + case kFeedFilterOperator_Equals: + return L"is"; + case kFeedFilterOperator_NotEquals: + return L"is not"; + case kFeedFilterOperator_IsGreaterThan: + return L"is greater than"; + case kFeedFilterOperator_IsGreaterThanOrEqualTo: + return L"is greater than or equal to"; + case kFeedFilterOperator_IsLessThan: + return L"is less than"; + case kFeedFilterOperator_IsLessThanOrEqualTo: + return L"is less than or equal to"; + case kFeedFilterOperator_BeginsWith: + return L"begins with"; + case kFeedFilterOperator_EndsWith: + return L"ends with"; + case kFeedFilterOperator_Contains: + return L"contains"; + case kFeedFilterOperator_NotContains: + return L"does not contain"; + default: + return L"?"; + } +} + +std::wstring FeedFilterManager::TranslateValue( + const FeedFilterCondition& condition) { + switch (condition.element) { + case kFeedFilterElement_Meta_Id: { + if (condition.value.empty()) { + return L"(?)"; + } else { + auto anime_item = AnimeDatabase.FindItem(ToInt(condition.value)); + if (anime_item) { + return condition.value + L" (" + anime_item->GetTitle() + L")"; + } else { + return condition.value + L" (?)"; + } + } + } + case kFeedFilterElement_User_Status: + return anime::TranslateMyStatus(ToInt(condition.value), false); + case kFeedFilterElement_Meta_Status: + return anime::TranslateStatus(ToInt(condition.value)); + case kFeedFilterElement_Meta_Type: + return anime::TranslateType(ToInt(condition.value)); + default: + return condition.value; + } +} + +std::wstring FeedFilterManager::TranslateMatching(int match) { + switch (match) { + case kFeedFilterMatchAll: + return L"All conditions"; + case kFeedFilterMatchAny: + return L"Any condition"; + default: + return L"?"; + } +} + +std::wstring FeedFilterManager::TranslateAction(int action) { + switch (action) { + case kFeedFilterActionDiscard: + return L"Discard matched items"; + case kFeedFilterActionSelect: + return L"Select matched items"; + case kFeedFilterActionPrefer: + return L"Prefer matched items to similar ones"; + default: + return L"?"; + } +} + +std::wstring FeedFilterManager::TranslateOption(int option) { + switch (option) { + case kFeedFilterOptionDefault: + return L"Default"; + case kFeedFilterOptionDeactivate: + return L"Deactivate discarded items"; + case kFeedFilterOptionHide: + return L"Hide discarded items"; + default: + return L"?"; + } +} + +std::wstring FeedFilterManager::GetShortcodeFromIndex( + FeedFilterShortcodeType type, int index) { + switch (type) { + case kFeedFilterShortcodeAction: + return action_shortcodes_[index]; + case kFeedFilterShortcodeElement: + return element_shortcodes_[index]; + case kFeedFilterShortcodeMatch: + return match_shortcodes_[index]; + case kFeedFilterShortcodeOperator: + return operator_shortcodes_[index]; + case kFeedFilterShortcodeOption: + return option_shortcodes_[index]; + } + + return std::wstring(); +} + +int FeedFilterManager::GetIndexFromShortcode(FeedFilterShortcodeType type, + const std::wstring& shortcode) { + std::map* shortcodes = nullptr; + switch (type) { + case kFeedFilterShortcodeAction: + shortcodes = &action_shortcodes_; + break; + case kFeedFilterShortcodeElement: + shortcodes = &element_shortcodes_; + break; + case kFeedFilterShortcodeMatch: + shortcodes = &match_shortcodes_; + break; + case kFeedFilterShortcodeOperator: + shortcodes = &operator_shortcodes_; + break; + case kFeedFilterShortcodeOption: + shortcodes = &option_shortcodes_; + break; + } + + foreach_(it, *shortcodes) + if (IsEqual(it->second, shortcode)) + return it->first; + + LOG(LevelDebug, L"Shortcode: \"" + shortcode + + L"\" for type \"" + ToWstr(type) + L"\" is not found."); + + return -1; +} \ No newline at end of file diff --git a/src/track/feed_filter.h b/src/track/feed_filter.h new file mode 100644 index 000000000..4b78f5223 --- /dev/null +++ b/src/track/feed_filter.h @@ -0,0 +1,180 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TRACK_FEED_FILTER_H +#define TAIGA_TRACK_FEED_FILTER_H + +#include +#include +#include + +enum FeedFilterElement { + kFeedFilterElement_None = -1, + kFeedFilterElement_Meta_Id, + kFeedFilterElement_Meta_Status, + kFeedFilterElement_Meta_Type, + kFeedFilterElement_Meta_Episodes, + kFeedFilterElement_Meta_DateStart, + kFeedFilterElement_Meta_DateEnd, + kFeedFilterElement_User_Status, + kFeedFilterElement_Local_EpisodeAvailable, + kFeedFilterElement_Episode_Title, + kFeedFilterElement_Episode_Number, + kFeedFilterElement_Episode_Version, + kFeedFilterElement_Episode_Group, + kFeedFilterElement_Episode_VideoResolution, + kFeedFilterElement_Episode_VideoType, + kFeedFilterElement_File_Title, + kFeedFilterElement_File_Category, + kFeedFilterElement_File_Description, + kFeedFilterElement_File_Link, + kFeedFilterElement_Count +}; + +enum FeedFilterOperator { + kFeedFilterOperator_Equals, + kFeedFilterOperator_NotEquals, + kFeedFilterOperator_IsGreaterThan, + kFeedFilterOperator_IsGreaterThanOrEqualTo, + kFeedFilterOperator_IsLessThan, + kFeedFilterOperator_IsLessThanOrEqualTo, + kFeedFilterOperator_BeginsWith, + kFeedFilterOperator_EndsWith, + kFeedFilterOperator_Contains, + kFeedFilterOperator_NotContains, + kFeedFilterOperator_Count +}; + +enum FeedFilterMatch { + kFeedFilterMatchAll, + kFeedFilterMatchAny +}; + +enum FeedFilterAction { + kFeedFilterActionDiscard, + kFeedFilterActionSelect, + kFeedFilterActionPrefer +}; + +enum FeedFilterOption { + kFeedFilterOptionDefault, + kFeedFilterOptionDeactivate, + kFeedFilterOptionHide +}; + +enum FeedFilterShortcodeType { + kFeedFilterShortcodeAction, + kFeedFilterShortcodeElement, + kFeedFilterShortcodeMatch, + kFeedFilterShortcodeOperator, + kFeedFilterShortcodeOption +}; + +class Feed; +class FeedItem; + +class FeedFilterCondition { +public: + FeedFilterCondition(); + ~FeedFilterCondition() {} + + FeedFilterCondition& operator=(const FeedFilterCondition& condition); + + void Reset(); + +public: + FeedFilterElement element; + FeedFilterOperator op; + std::wstring value; +}; + +class FeedFilter { +public: + FeedFilter(); + ~FeedFilter() {} + + FeedFilter& operator=(const FeedFilter& filter); + + void AddCondition(FeedFilterElement element, FeedFilterOperator op, const std::wstring& value); + void Filter(Feed& feed, FeedItem& item, bool recursive); + void Reset(); + +public: + std::wstring name; + bool enabled; + + FeedFilterAction action; + FeedFilterMatch match; + FeedFilterOption option; + + std::vector anime_ids; + std::vector conditions; +}; + +class FeedFilterPreset { +public: + FeedFilterPreset(); + ~FeedFilterPreset() {} + + std::wstring description; + FeedFilter filter; + bool is_default; +}; + +class FeedFilterManager { +public: + FeedFilterManager(); + ~FeedFilterManager() {} + + void InitializePresets(); + void InitializeShortcodes(); + + void AddPresets(); + void AddFilter(FeedFilterAction action, FeedFilterMatch match, FeedFilterOption option, bool enabled, const std::wstring& name); + void Cleanup(); + void Filter(Feed& feed, bool preferences); + void FilterArchived(Feed& feed); + bool IsItemDownloadAvailable(Feed& feed); + void MarkNewEpisodes(Feed& feed); + + std::wstring CreateNameFromConditions(const FeedFilter& filter); + std::wstring TranslateCondition(const FeedFilterCondition& condition); + std::wstring TranslateConditions(const FeedFilter& filter, size_t index); + std::wstring TranslateElement(int element); + std::wstring TranslateOperator(int op); + std::wstring TranslateValue(const FeedFilterCondition& condition); + std::wstring TranslateMatching(int match); + std::wstring TranslateAction(int action); + std::wstring TranslateOption(int option); + + std::wstring GetShortcodeFromIndex(FeedFilterShortcodeType type, int index); + int GetIndexFromShortcode(FeedFilterShortcodeType type, const std::wstring& shortcode); + +public: + std::vector filters; + std::vector presets; + +private: + std::map action_shortcodes_; + std::map element_shortcodes_; + std::map match_shortcodes_; + std::map operator_shortcodes_; + std::map option_shortcodes_; +}; + +#endif // TAIGA_TRACK_FEED_FILTER_H \ No newline at end of file diff --git a/src/track/media.cpp b/src/track/media.cpp new file mode 100644 index 000000000..46ad4522e --- /dev/null +++ b/src/track/media.cpp @@ -0,0 +1,447 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include +#include + +#include "base/file.h" +#include "base/foreach.h" +#include "base/process.h" +#include "base/string.h" +#include "base/xml.h" +#include "library/anime.h" +#include "library/anime_db.h" +#include "library/anime_episode.h" +#include "library/anime_util.h" +#include "taiga/path.h" +#include "taiga/settings.h" +#include "taiga/timer.h" +#include "track/media.h" +#include "track/recognition.h" +#include "ui/dlg/dlg_anime_info.h" +#include "ui/dialog.h" +#include "ui/ui.h" + +class MediaPlayers MediaPlayers; + +MediaPlayers::MediaPlayers() + : player_running_(false), + title_changed_(false) { +} + +bool MediaPlayers::Load() { + items.clear(); + + xml_document document; + std::wstring path = taiga::GetPath(taiga::kPathMedia); + xml_parse_result parse_result = document.load_file(path.c_str()); + + if (parse_result.status != pugi::status_ok) { + MessageBox(nullptr, L"Could not read media list.", + path.c_str(), MB_OK | MB_ICONERROR); + return false; + } + + // Read player list + xml_node mediaplayers = document.child(L"media_players"); + foreach_xmlnode_(player, mediaplayers, L"player") { + items.resize(items.size() + 1); + items.back().name = XmlReadStrValue(player, L"name"); + items.back().enabled = XmlReadIntValue(player, L"enabled"); + items.back().engine = XmlReadStrValue(player, L"engine"); + items.back().visible = XmlReadIntValue(player, L"visible"); + items.back().mode = XmlReadIntValue(player, L"mode"); + XmlReadChildNodes(player, items.back().classes, L"class"); + XmlReadChildNodes(player, items.back().files, L"file"); + XmlReadChildNodes(player, items.back().folders, L"folder"); + for (xml_node child_node = player.child(L"edit"); child_node; + child_node = child_node.next_sibling(L"edit")) { + MediaPlayer::EditTitle edit; + edit.mode = child_node.attribute(L"mode").as_int(); + edit.value = child_node.child_value(); + items.back().edits.push_back(edit); + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +MediaPlayer* MediaPlayers::FindPlayer(const std::wstring& name) { + if (!name.empty()) + foreach_(item, items) + if (item->name == name) + return &(*item); + + return nullptr; +} + +HWND MediaPlayers::GetCurrentWindowHandle() { + auto media_player = FindPlayer(current_player_); + if (media_player) + return media_player->window_handle; + + return nullptr; +} + +std::wstring MediaPlayers::current_player() const { + return current_player_; +} + +bool MediaPlayers::player_running() const { + return player_running_; +} + +void MediaPlayers::set_player_running(bool player_running) { + player_running_ = player_running; +} + +std::wstring MediaPlayers::current_title() const { + return current_title_; +} + +void MediaPlayers::set_current_title(const std::wstring& title) { + if (current_title_ != title) { + current_title_ = title; + set_title_changed(true); + } +} + +bool MediaPlayers::title_changed() const { + return title_changed_; +} + +void MediaPlayers::set_title_changed(bool title_changed) { + title_changed_ = title_changed; +} + +//////////////////////////////////////////////////////////////////////////////// + +MediaPlayer* MediaPlayers::CheckRunningPlayers() { + current_player_.clear(); + + bool recognized = CurrentEpisode.anime_id > anime::ID_UNKNOWN; + + // Go through windows, starting with the highest in the Z-order + HWND hwnd = GetWindow(ui::GetWindowHandle(ui::kDialogMain), GW_HWNDFIRST); + while (hwnd != nullptr) { + foreach_(item, items) { + if (!item->enabled) + continue; + if (item->visible && !IsWindowVisible(hwnd)) + continue; + + // Compare window classes + foreach_(window_class, item->classes) { + if (*window_class == GetWindowClass(hwnd)) { + // Compare file names + foreach_(file, item->files) { + if (IsEqual(*file, GetFileName(GetWindowPath(hwnd)))) { + if (item->mode != kMediaModeWebBrowser || + !GetWindowTitle(hwnd).empty()) { + // Stick with the previously recognized window, if there is one + if (!recognized || item->window_handle == hwnd) { + // We have a match! + player_running_ = true; + current_player_ = item->name; + std::wstring title = GetTitle(hwnd, *window_class, item->mode); + EditTitle(title, &(*item)); + set_current_title(title); + item->window_handle = hwnd; + return &(*item); + } + } + } + } + } + } + } + + // Check next window + hwnd = GetWindow(hwnd, GW_HWNDNEXT); + } + + // Not found + return nullptr; +} + +MediaPlayer* MediaPlayers::GetRunningPlayer() { + return FindPlayer(current_player_); +} + +void MediaPlayers::EditTitle(std::wstring& str, + const MediaPlayer* media_player) { + if (str.empty() || !media_player || media_player->edits.empty()) + return; + + foreach_(it, media_player->edits) { + switch (it->mode) { + // Erase + case 1: { + Replace(str, it->value, L"", false, true); + break; + } + // Cut right side + case 2: { + int pos = InStr(str, it->value, 0); + if (pos > -1) + str.resize(pos); + break; + } + } + } + + TrimRight(str, L" -"); +} + +std::wstring MediaPlayer::GetPath() const { + foreach_(folder, folders) { + foreach_(file, files) { + std::wstring path = *folder + *file; + path = ExpandEnvironmentStrings(path); + if (FileExists(path)) + return path; + } + } + + return std::wstring(); +} + +bool MediaPlayer::IsActive() const { + if (!Settings.GetBool(taiga::kSync_Update_CheckPlayer)) + return true; + + return window_handle == GetForegroundWindow(); +} + +std::wstring MediaPlayers::GetTitle(HWND hwnd, + const std::wstring& class_name, + int mode) { + switch (mode) { + // File handle + case kMediaModeFileHandle: + return GetTitleFromProcessHandle(hwnd); + // Winamp API + case kMediaModeWinampApi: + return GetTitleFromWinampAPI(hwnd, false); + // Special message + case kMediaModeSpecialMessage: + return GetTitleFromSpecialMessage(hwnd, class_name); + // MPlayer + case kMediaModeMplayer: + return GetTitleFromMPlayer(); + // Browser + case kMediaModeWebBrowser: + return GetTitleFromBrowser(hwnd); + // Window title + case kMediaModeWindowTitle: + default: + return GetWindowTitle(hwnd); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void ProcessMediaPlayerStatus(const MediaPlayer* media_player) { + // Media player is running + if (media_player) { + ProcessMediaPlayerTitle(*media_player); + + // Media player is not running + } else { + auto anime_item = AnimeDatabase.FindItem(CurrentEpisode.anime_id); + + // Media player was running, and the media was recognized + if (anime_item) { + bool processed = CurrentEpisode.processed; // TODO: temporary solution... + CurrentEpisode.Set(anime::ID_UNKNOWN); + EndWatching(*anime_item, CurrentEpisode); + if (Settings.GetBool(taiga::kSync_Update_WaitPlayer)) { + CurrentEpisode.anime_id = anime_item->GetId(); + CurrentEpisode.processed = processed; + UpdateList(*anime_item, CurrentEpisode); + CurrentEpisode.anime_id = anime::ID_UNKNOWN; + } + taiga::timers.timer(taiga::kTimerMedia)->Reset(); + + // Media player was running, but not watching + } else if (MediaPlayers.player_running()) { + ui::ClearStatusText(); + CurrentEpisode.Set(anime::ID_UNKNOWN); + MediaPlayers.set_player_running(false); + ui::DlgNowPlaying.SetCurrentId(anime::ID_UNKNOWN); + } + } +} + +void ProcessMediaPlayerTitle(const MediaPlayer& media_player) { + auto anime_item = AnimeDatabase.FindItem(CurrentEpisode.anime_id); + + if (CurrentEpisode.anime_id == anime::ID_UNKNOWN) { + if (!Settings.GetBool(taiga::kApp_Option_EnableRecognition)) + return; + // Examine title and compare it with list items + if (Meow.ExamineTitle(MediaPlayers.current_title(), CurrentEpisode)) { + anime_item = Meow.MatchDatabase(CurrentEpisode, + false, true, true, true, true, true); + if (anime_item) { + // Recognized + MediaPlayers.set_title_changed(false); + CurrentEpisode.Set(anime_item->GetId()); + StartWatching(*anime_item, CurrentEpisode); + return; + } + } + // Not recognized + CurrentEpisode.Set(anime::ID_NOTINLIST); + ui::OnRecognitionFail(); + + } else { + if (MediaPlayers.title_changed()) { + // Caption changed + MediaPlayers.set_title_changed(false); + ui::ClearStatusText(); + bool processed = CurrentEpisode.processed; // TODO: not a good solution... + CurrentEpisode.Set(anime::ID_UNKNOWN); + if (anime_item) { + EndWatching(*anime_item, CurrentEpisode); + CurrentEpisode.anime_id = anime_item->GetId(); + CurrentEpisode.processed = processed; + UpdateList(*anime_item, CurrentEpisode); + CurrentEpisode.anime_id = anime::ID_UNKNOWN; + } + taiga::timers.timer(taiga::kTimerMedia)->Reset(); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +// BS.Player +#define BSP_CLASS L"BSPlayer" +#define BSP_GETFILENAME 0x1010B + +// Winamp +#define IPC_ISPLAYING 104 +#define IPC_GETLISTPOS 125 +#define IPC_GETPLAYLISTFILE 211 +#define IPC_GETPLAYLISTFILEW 214 + +std::wstring MediaPlayers::GetTitleFromProcessHandle(HWND hwnd, ULONG process_id) { + if (hwnd != NULL && process_id == 0) + GetWindowThreadProcessId(hwnd, &process_id); + + std::vector files_vector; + + if (GetProcessFiles(process_id, files_vector)) { + foreach_(it, files_vector) { + if (CheckFileExtension(GetFileExtension(*it), Meow.valid_extensions)) { + if (it->at(1) != L':') { + TranslateDeviceName(*it); + } + if (it->at(1) == L':') { + WCHAR buffer[4096] = {0}; + GetLongPathName(it->c_str(), buffer, 4096); + return std::wstring(buffer); + } else { + return GetFileName(*it); + } + } + } + } + + return std::wstring(); +} + +std::wstring MediaPlayers::GetTitleFromWinampAPI(HWND hwnd, bool use_unicode) { + if (IsWindow(hwnd)) { + if (SendMessage(hwnd, WM_USER, 0, IPC_ISPLAYING)) { + int list_index = SendMessage(hwnd, WM_USER, 0, IPC_GETLISTPOS); + int base_address = SendMessage(hwnd, WM_USER, list_index, + use_unicode ? IPC_GETPLAYLISTFILEW : IPC_GETPLAYLISTFILE); + if (base_address) { + DWORD process_id; + GetWindowThreadProcessId(hwnd, &process_id); + HANDLE hwnd_winamp = OpenProcess(PROCESS_VM_READ, FALSE, process_id); + if (hwnd_winamp) { + if (use_unicode) { + wchar_t file_name[MAX_PATH]; + ReadProcessMemory(hwnd_winamp, + reinterpret_cast(base_address), + file_name, MAX_PATH, NULL); + CloseHandle(hwnd_winamp); + return file_name; + } else { + char file_name[MAX_PATH]; + ReadProcessMemory(hwnd_winamp, + reinterpret_cast(base_address), + file_name, MAX_PATH, NULL); + CloseHandle(hwnd_winamp); + return StrToWstr(file_name); + } + } + } + } + } + + return std::wstring(); +} + +std::wstring MediaPlayers::GetTitleFromSpecialMessage( + HWND hwnd, + const std::wstring& class_name) { + // BS.Player + if (class_name == BSP_CLASS) { + if (IsWindow(hwnd)) { + COPYDATASTRUCT cds; + char file_name[MAX_PATH]; + void* data = &file_name; + cds.dwData = BSP_GETFILENAME; + cds.lpData = &data; + cds.cbData = 4; + SendMessage(hwnd, WM_COPYDATA, + reinterpret_cast(ui::GetWindowHandle(ui::kDialogMain)), + reinterpret_cast(&cds)); + return StrToWstr(file_name); + } + } + + return std::wstring(); +} + +std::wstring MediaPlayers::GetTitleFromMPlayer() { + std::wstring title; + + HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hProcessSnap != INVALID_HANDLE_VALUE) { + PROCESSENTRY32 pe32; + pe32.dwSize = sizeof(PROCESSENTRY32); + if (Process32First(hProcessSnap, &pe32)) { + do { + if (IsEqual(pe32.szExeFile, L"mplayer.exe")) { + title = GetTitleFromProcessHandle(NULL, pe32.th32ProcessID); + break; + } + } while (Process32Next(hProcessSnap, &pe32)); + } + CloseHandle(hProcessSnap); + } + + return title; +} \ No newline at end of file diff --git a/src/track/media.h b/src/track/media.h new file mode 100644 index 000000000..53eed24a4 --- /dev/null +++ b/src/track/media.h @@ -0,0 +1,111 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TRACK_MEDIA_H +#define TAIGA_TRACK_MEDIA_H + +#include +#include + +#include "base/accessibility.h" + +enum MediaPlayerModes { + kMediaModeWindowTitle, + kMediaModeFileHandle, + kMediaModeWinampApi, + kMediaModeSpecialMessage, + kMediaModeMplayer, + kMediaModeWebBrowser +}; + +class MediaPlayer { +public: + std::wstring GetPath() const; + bool IsActive() const; + + std::wstring name; + BOOL enabled; + BOOL visible; + int mode; + std::vector classes; + std::vector files; + std::vector folders; + std::wstring engine; + + struct EditTitle { + int mode; + std::wstring value; + }; + std::vector edits; + + HWND window_handle; +}; + +class MediaPlayers { +public: + MediaPlayers(); + ~MediaPlayers() {} + + bool Load(); + + MediaPlayer* FindPlayer(const std::wstring& name); + HWND GetCurrentWindowHandle(); + + std::wstring current_player() const; + bool player_running() const; + void set_player_running(bool player_running); + std::wstring current_title() const; + void set_current_title(const std::wstring& title); + bool title_changed() const; + void set_title_changed(bool title_changed); + + MediaPlayer* CheckRunningPlayers(); + MediaPlayer* GetRunningPlayer(); + + void EditTitle(std::wstring& str, const MediaPlayer* media_player); + std::wstring GetTitle(HWND hwnd, const std::wstring& class_name, int mode); + + std::wstring GetTitleFromProcessHandle(HWND hwnd, ULONG process_id = 0); + std::wstring GetTitleFromWinampAPI(HWND hwnd, bool use_unicode); + std::wstring GetTitleFromSpecialMessage(HWND hwnd, const std::wstring& class_name); + std::wstring GetTitleFromMPlayer(); + std::wstring GetTitleFromBrowser(HWND hwnd); + std::wstring GetTitleFromStreamingMediaProvider(const std::wstring& url, std::wstring& title); + +public: + std::vector items; + + class BrowserAccessibleObject : public base::AccessibleObject { + public: + bool AllowChildTraverse(base::AccessibleChild& child, LPARAM param = 0L); + } acc_obj; + +private: + std::wstring current_player_; + bool player_running_; + + std::wstring current_title_; + bool title_changed_; +}; + +extern MediaPlayers MediaPlayers; + +void ProcessMediaPlayerStatus(const MediaPlayer* media_player); +void ProcessMediaPlayerTitle(const MediaPlayer& media_player); + +#endif // TAIGA_TRACK_MEDIA_H \ No newline at end of file diff --git a/src/track/media_stream.cpp b/src/track/media_stream.cpp new file mode 100644 index 000000000..113dcbd99 --- /dev/null +++ b/src/track/media_stream.cpp @@ -0,0 +1,359 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "base/process.h" +#include "base/string.h" +#include "library/anime_episode.h" +#include "taiga/settings.h" +#include "track/media.h" + +enum StreamingVideoProvider { + kStreamUnknown = -1, + kStreamAnn, + kStreamCrunchyroll, + kStreamHulu, + kStreamVeoh, + kStreamVizanime, + kStreamYoutube +}; + +enum WebBrowserEngine { + kWebEngineUnknown = -1, + kWebEngineWebkit, // Google Chrome (and other browsers based on Chromium) + kWebEngineGecko, // Mozilla Firefox + kWebEngineTrident, // Internet Explorer + kWebEnginePresto // Opera (older versions) +}; + +class BrowserAccessibilityData { +public: + BrowserAccessibilityData(const std::wstring& name, DWORD role); + + std::wstring name; + DWORD role; +}; + +std::map> browser_data; + +//////////////////////////////////////////////////////////////////////////////// + +BrowserAccessibilityData::BrowserAccessibilityData(const std::wstring& name, + DWORD role) + : name(name), role(role) { +} + +void AddBrowserData(WebBrowserEngine browser, + const std::wstring& name, DWORD role) { + browser_data[browser].push_back(BrowserAccessibilityData(name, role)); +} + +void InitializeBrowserData() { + if (!browser_data.empty()) + return; + + AddBrowserData(kWebEngineWebkit, + L"Address and search bar", ROLE_SYSTEM_GROUPING); + AddBrowserData(kWebEngineWebkit, + L"Address", ROLE_SYSTEM_GROUPING); + AddBrowserData(kWebEngineWebkit, + L"Location", ROLE_SYSTEM_GROUPING); + AddBrowserData(kWebEngineWebkit, + L"Address field", ROLE_SYSTEM_TEXT); + + AddBrowserData(kWebEngineGecko, + L"Search or enter address", ROLE_SYSTEM_TEXT); + AddBrowserData(kWebEngineGecko, + L"Go to a Website", ROLE_SYSTEM_TEXT); + AddBrowserData(kWebEngineGecko, + L"Go to a Web Site", ROLE_SYSTEM_TEXT); + + AddBrowserData(kWebEngineTrident, + L"Address and search using Bing", ROLE_SYSTEM_TEXT); + AddBrowserData(kWebEngineTrident, + L"Address and search using Google", ROLE_SYSTEM_TEXT); +} + +//////////////////////////////////////////////////////////////////////////////// + +base::AccessibleChild* FindAccessibleChild( + std::vector& children, + const std::wstring& name, + DWORD role) { + base::AccessibleChild* child = nullptr; + + foreach_(it, children) { + if (name.empty() || IsEqual(name, it->name)) + if (!role || role == it->role) + child = &(*it); + if (child == nullptr && !it->children.empty()) + child = FindAccessibleChild(it->children, name, role); + if (child) + break; + } + + return child; +} + +bool MediaPlayers::BrowserAccessibleObject::AllowChildTraverse( + base::AccessibleChild& child, + LPARAM param) { + switch (param) { + case kWebEngineUnknown: + return false; + + case kWebEngineWebkit: + switch (child.role) { + case ROLE_SYSTEM_CLIENT: + case ROLE_SYSTEM_GROUPING: + case ROLE_SYSTEM_PAGETABLIST: + case ROLE_SYSTEM_TEXT: + case ROLE_SYSTEM_TOOLBAR: + case ROLE_SYSTEM_WINDOW: + return true; + default: + return false; + } + break; + + case kWebEngineGecko: + switch (child.role) { + case ROLE_SYSTEM_APPLICATION: + case ROLE_SYSTEM_COMBOBOX: + case ROLE_SYSTEM_PAGETABLIST: + case ROLE_SYSTEM_TOOLBAR: + return true; + case ROLE_SYSTEM_DOCUMENT: + default: + return false; + } + break; + + case kWebEngineTrident: + switch (child.role) { + case ROLE_SYSTEM_PANE: + case ROLE_SYSTEM_SCROLLBAR: + return false; + } + break; + + case kWebEnginePresto: + switch (child.role) { + case ROLE_SYSTEM_DOCUMENT: + case ROLE_SYSTEM_PANE: + return false; + } + break; + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +std::wstring MediaPlayers::GetTitleFromBrowser(HWND hwnd) { + WebBrowserEngine web_engine = kWebEngineUnknown; + + auto media_player = FindPlayer(current_player()); + + // Get window title + std::wstring title = GetWindowTitle(hwnd); + EditTitle(title, media_player); + + // Return current title if the same web page is still open + if (CurrentEpisode.anime_id > 0) + if (InStr(title, current_title()) > -1) + return current_title(); + + // Delay operation to save some CPU + static int counter = 0; + if (counter < 5) { + counter++; + return current_title(); + } else { + counter = 0; + } + + // Select web browser engine + if (media_player->engine == L"WebKit") { + web_engine = kWebEngineWebkit; + } else if (media_player->engine == L"Gecko") { + web_engine = kWebEngineGecko; + } else if (media_player->engine == L"Trident") { + web_engine = kWebEngineTrident; + } else if (media_player->engine == L"Presto") { + web_engine = kWebEnginePresto; + } else { + return std::wstring(); + } + + // Build accessibility data + acc_obj.children.clear(); + if (acc_obj.FromWindow(hwnd) == S_OK) { + acc_obj.BuildChildren(acc_obj.children, nullptr, web_engine); + acc_obj.Release(); + } + + // Check other tabs + if (CurrentEpisode.anime_id > 0) { + base::AccessibleChild* child = nullptr; + switch (web_engine) { + case kWebEngineWebkit: + case kWebEngineGecko: + child = FindAccessibleChild(acc_obj.children, + L"", ROLE_SYSTEM_PAGETABLIST); + break; + case kWebEngineTrident: + child = FindAccessibleChild(acc_obj.children, + L"Tab Row", 0); + break; + case kWebEnginePresto: + child = FindAccessibleChild(acc_obj.children, + L"", ROLE_SYSTEM_CLIENT); + break; + } + if (child) { + foreach_(it, child->children) { + // FIXME: Doesn't work with ANN because of "(s)" or "(d)" + if (InStr(it->name, current_title()) > -1) { + // Tab is still open, just not active + return current_title(); + } + } + } + // Tab is closed + return std::wstring(); + } + + // Find URL + base::AccessibleChild* child = nullptr; + switch (web_engine) { + case kWebEngineWebkit: + case kWebEngineGecko: + case kWebEngineTrident: { + InitializeBrowserData(); + auto& child_data = browser_data[web_engine]; + foreach_(it, child_data) { + child = FindAccessibleChild(acc_obj.children, it->name, it->role); + if (child) + break; + } + break; + } + case kWebEnginePresto: + child = FindAccessibleChild(acc_obj.children, + L"", ROLE_SYSTEM_CLIENT); + if (child && !child->children.empty()) { + child = FindAccessibleChild(child->children.at(0).children, + L"", ROLE_SYSTEM_TOOLBAR); + if (child && !child->children.empty()) { + child = FindAccessibleChild(child->children, + L"", ROLE_SYSTEM_COMBOBOX); + if (child && !child->children.empty()) { + child = FindAccessibleChild(child->children, + L"", ROLE_SYSTEM_TEXT); + } + } + } + break; + } + + if (child) { + title = GetTitleFromStreamingMediaProvider(child->value, title); + } else { + title.clear(); + } + + return title; +} + +std::wstring MediaPlayers::GetTitleFromStreamingMediaProvider( + const std::wstring& url, + std::wstring& title) { + StreamingVideoProvider stream_provider = kStreamUnknown; + + // Check URL for known streaming video providers + if (!url.empty()) { + // Anime News Network + if (Settings.GetBool(taiga::kStream_Ann) && + InStr(url, L"animenewsnetwork.com/video") > -1) { + stream_provider = kStreamAnn; + // Crunchyroll + } else if (Settings.GetBool(taiga::kStream_Crunchyroll) && + InStr(url, L"crunchyroll.com/") > -1) { + stream_provider = kStreamCrunchyroll; + // Hulu + /* + } else if (InStr(url, L"hulu.com/watch") > -1) { + stream_provider = kStreamHulu; + */ + // Veoh + } else if (Settings.GetBool(taiga::kStream_Veoh) && + InStr(url, L"veoh.com/watch") > -1) { + stream_provider = kStreamVeoh; + // Viz Anime + } else if (Settings.GetBool(taiga::kStream_Viz) && + InStr(url, L"vizanime.com/ep") > -1) { + stream_provider = kStreamVizanime; + // YouTube + } else if (Settings.GetBool(taiga::kStream_Youtube) && + InStr(url, L"youtube.com/watch") > -1) { + stream_provider = kStreamYoutube; + } + } + + // Clean-up title + switch (stream_provider) { + // Anime News Network + case kStreamAnn: + EraseRight(title, L" - Anime News Network"); + Erase(title, L" (s)"); + Erase(title, L" (d)"); + break; + // Crunchyroll + case kStreamCrunchyroll: + EraseLeft(title, L"Crunchyroll - Watch "); + break; + // Hulu + case kStreamHulu: + EraseLeft(title, L"Watch "); + EraseRight(title, L" online | Free | Hulu"); + EraseRight(title, L" online | Plus | Hulu"); + break; + // Veoh + case kStreamVeoh: + EraseLeft(title, L"Watch Videos Online | "); + EraseRight(title, L" | Veoh.com"); + break; + // Viz Anime + case kStreamVizanime: + EraseRight(title, L" - VIZ ANIME: Free Online Anime - All The Time"); + break; + // YouTube + case kStreamYoutube: + EraseRight(title, L" - YouTube"); + break; + // Some other website, or URL is not found + default: + case kStreamUnknown: + title.clear(); + break; + } + + return title; +} \ No newline at end of file diff --git a/src/track/monitor.cpp b/src/track/monitor.cpp new file mode 100644 index 000000000..77d158576 --- /dev/null +++ b/src/track/monitor.cpp @@ -0,0 +1,392 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/file.h" +#include "base/foreach.h" +#include "base/log.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/anime_episode.h" +#include "library/anime_util.h" +#include "taiga/settings.h" +#include "track/monitor.h" +#include "track/recognition.h" +#include "track/search.h" + +class FolderMonitor FolderMonitor; + +FolderInfo::FolderInfo() + : bytes_returned_(0), + directory_handle_(INVALID_HANDLE_VALUE), + notify_filter_(FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME), + state(kFolderMonitorStateStopped), + watch_subtree_(TRUE) { + ZeroMemory(&overlapped_, sizeof(overlapped_)); +} + +FolderInfo::FolderChangeInfo::FolderChangeInfo() + : action(0), parameter(0), type(kPathTypeFile) { +} + +//////////////////////////////////////////////////////////////////////////////// + +FolderMonitor::FolderMonitor() + : completion_port_(nullptr), + window_handle_(nullptr) { +} + +FolderMonitor::~FolderMonitor() { + Stop(); + ClearFolders(); + + if (completion_port_) { + ::CloseHandle(completion_port_); + completion_port_ = nullptr; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +bool FolderMonitor::AddFolder(const std::wstring& folder) { + if (!FolderExists(folder)) + return false; + + HANDLE handle = ::CreateFile( + folder.c_str(), + FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + nullptr); + + if (handle == INVALID_HANDLE_VALUE) + return false; + + folders_.resize(folders_.size() + 1); + folders_.back().directory_handle_ = handle; + folders_.back().path = folder; + + return true; +} + +bool FolderMonitor::ClearFolders() { + // Close handles + foreach_(folder, folders_) { + if (folder->directory_handle_ != INVALID_HANDLE_VALUE) { + ::CloseHandle(folder->directory_handle_); + folder->directory_handle_ = INVALID_HANDLE_VALUE; + } + } + + folders_.clear(); + + return true; +} + +bool FolderMonitor::Start() { + // Create worker thread + if (!GetThreadHandle()) + CreateThread(nullptr, 0, 0); + + // Start watching folders + if (GetThreadHandle()) { + foreach_(folder, folders_) { + completion_port_ = ::CreateIoCompletionPort( + folder->directory_handle_, + completion_port_, + reinterpret_cast(&(*folder)), + 0); + if (completion_port_) + ::PostQueuedCompletionStatus( + completion_port_, + sizeof(*folder), + reinterpret_cast(&(*folder)), + &folder->overlapped_); + } + } + + return GetThreadHandle() != nullptr; +} + +void FolderMonitor::Stop() { + if (GetThreadHandle()) { + // Signal worker thread to stop + ::PostQueuedCompletionStatus(completion_port_, 0, 0, nullptr); + + // Wait for thread to stop + ::WaitForSingleObject(GetThreadHandle(), INFINITE); + + // Clean up + CloseThreadHandle(); + ::CloseHandle(completion_port_); + completion_port_ = nullptr; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL FolderMonitor::ReadDirectoryChanges(FolderInfo& folder_info) const { + return ::ReadDirectoryChangesW(folder_info.directory_handle_, + folder_info.buffer_, + MONITOR_BUFFER_SIZE, + folder_info.watch_subtree_, + folder_info.notify_filter_, + &folder_info.bytes_returned_, + &folder_info.overlapped_, + nullptr); +} + +DWORD FolderMonitor::ThreadProc() { + DWORD dwNumBytes = 0; + FolderInfo* folder_info = nullptr; + LPOVERLAPPED lpOverlapped; + + do { + ::GetQueuedCompletionStatus(GetCompletionPort(), + &dwNumBytes, + reinterpret_cast(&folder_info), + &lpOverlapped, + INFINITE); + + if (folder_info) { + // Lock folder data + win::Lock lock(critical_section_); + + switch (folder_info->state) { + // Start monitoring + case kFolderMonitorStateStopped: { + if (ReadDirectoryChanges(*folder_info)) { + folder_info->state = kFolderMonitorStateActive; + LOG(LevelDebug, L"Started monitoring: " + folder_info->path); + } + break; + } + + // Change detected + case kFolderMonitorStateActive: { + DWORD dwNextEntryOffset = 0; + PFILE_NOTIFY_INFORMATION pfni = nullptr; + + do { + pfni = reinterpret_cast( + folder_info->buffer_ + dwNextEntryOffset); + // Retrieve changed file name + WCHAR file_name[MAX_PATH + 1] = {'\0'}; + CopyMemory(file_name, pfni->FileName, pfni->FileNameLength); + // Add item to list + folder_info->change_list.resize(folder_info->change_list.size() + 1); + folder_info->change_list.back().action = pfni->Action; + folder_info->change_list.back().file_name = file_name; + // Continue to next change + dwNextEntryOffset += pfni->NextEntryOffset; + } while (pfni->NextEntryOffset != 0); + + // Post a message to the main thread + if (window_handle_) + ::PostMessage(window_handle_, WM_MONITORCALLBACK, 0, + reinterpret_cast(folder_info)); + + // Continue monitoring + ReadDirectoryChanges(*folder_info); + break; + } + } + } + } while (folder_info); + + LOG(LevelDebug, L"Stopped monitoring."); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// + +void FolderMonitor::Enable(bool enabled) { + Stop(); + + if (enabled) { + ClearFolders(); + + foreach_(folder, Settings.root_folders) + AddFolder(*folder); + + Start(); + } +} + +HANDLE FolderMonitor::GetCompletionPort() const { + return completion_port_; +} + +void FolderMonitor::SetWindowHandle(HWND hwnd) { + window_handle_ = hwnd; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool FolderMonitor::IsPathAvailable(DWORD action) const { + switch (action) { + case FILE_ACTION_ADDED: + case FILE_ACTION_RENAMED_NEW_NAME: + return true; + case FILE_ACTION_REMOVED: + case FILE_ACTION_RENAMED_OLD_NAME: + default: + return false; + } +} + +void FolderMonitor::OnChange(FolderInfo& folder_info) { + // Lock folder data + win::Lock lock(critical_section_); + + foreach_(change_info, folder_info.change_list) { + switch (change_info->action) { + case FILE_ACTION_ADDED: + case FILE_ACTION_REMOVED: + case FILE_ACTION_RENAMED_OLD_NAME: + case FILE_ACTION_RENAMED_NEW_NAME: + break; + default: + continue; + } + + AddTrailingSlash(folder_info.path); + std::wstring path = folder_info.path + change_info->file_name; + + // Is it a file or a directory? + if (IsPathAvailable(change_info->action)) { + if (FolderExists(path)) + change_info->type = kPathTypeDirectory; + } else { + std::wstring file_extension = GetFileExtension(change_info->file_name); + if (!ValidateFileExtension(file_extension, 4)) + change_info->type = kPathTypeDirectory; + } + + switch (change_info->action) { + case FILE_ACTION_ADDED: + LOG(LevelDebug, L"Added: " + path); + break; + case FILE_ACTION_REMOVED: + LOG(LevelDebug, L"Removed: " + path); + break; + case FILE_ACTION_RENAMED_OLD_NAME: + LOG(LevelDebug, L"Renamed (old): " + path); + break; + case FILE_ACTION_RENAMED_NEW_NAME: + LOG(LevelDebug, L"Renamed (new): " + path); + break; + } + + size_t change_index = change_info - folder_info.change_list.begin(); + HandleAnime(path, folder_info, change_index); + } + + // Clear change list + folder_info.change_list.clear(); +} + +void ChangeAnimeFolder(anime::Item& anime_item, const std::wstring& path) { + anime_item.SetFolder(path); + Settings.Save(); + + LOG(LevelDebug, L"Anime folder changed: " + anime_item.GetTitle()); + LOG(LevelDebug, L"Path: " + anime_item.GetFolder()); + + ScanAvailableEpisodesQuick(anime_item.GetId()); +} + +void FolderMonitor::HandleAnime(const std::wstring& path, + FolderInfo& folder_info, + size_t change_index) { + auto& change_info = folder_info.change_list.at(change_index); + bool path_available = IsPathAvailable(change_info.action); + + int anime_id = anime::ID_UNKNOWN; + if (change_info.parameter) + anime_id = change_info.parameter; + + if (change_info.type == kPathTypeDirectory) { + // Compare with list item folders + if (!path_available) { + foreach_cr_(it, AnimeDatabase.items) { + if (!it->second.IsInList()) + continue; + if (!it->second.GetFolder().empty() && + IsEqual(it->second.GetFolder(), path)) { + anime_id = it->second.GetId(); + break; + } + } + if (anime_id != anime::ID_UNKNOWN) { + // Handle next change + if (change_index < folder_info.change_list.size() - 1) { + folder_info.change_list.at(change_index + 1).parameter = anime_id; + return; + } + } + } + + if (anime_id != anime::ID_UNKNOWN) { + // Change anime folder + auto anime_item = AnimeDatabase.FindItem(anime_id); + ChangeAnimeFolder(*anime_item, path_available ? path : L""); + return; + } + } + + // Examine path and compare with list items + anime::Episode episode; + if (Meow.ExamineTitle(path, episode)) { + if (anime_id == anime::ID_UNKNOWN || change_info.type == kPathTypeFile) { + auto anime_item = Meow.MatchDatabase(episode, true, true, true, false, false); + if (anime_item) + anime_id = anime_item->GetId(); + } + + if (anime_id != anime::ID_UNKNOWN) { + auto anime_item = AnimeDatabase.FindItem(anime_id); + + // Set anime folder + if (path_available && anime_item->GetFolder().empty()) { + if (change_info.type == kPathTypeDirectory) { + ChangeAnimeFolder(*anime_item, path); + } else if (!episode.folder.empty()) { + anime::Episode temp_episode; + temp_episode.title = episode.folder; + if (Meow.CompareEpisode(temp_episode, *anime_item)) + ChangeAnimeFolder(*anime_item, episode.folder); + } + } + + // Set episode availability + if (change_info.type == kPathTypeFile) { + int number = anime::GetEpisodeHigh(episode.number); + int number_low = anime::GetEpisodeLow(episode.number); + for (int j = number_low; j <= number; j++) { + if (anime_item->SetEpisodeAvailability(number, path_available, path)) { + LOG(LevelDebug, anime_item->GetTitle() + L" #" + ToWstr(j) + L" is " + + (path_available ? L"available." : L"unavailable.")); + } + } + } + } + } +} \ No newline at end of file diff --git a/monitor.h b/src/track/monitor.h similarity index 56% rename from monitor.h rename to src/track/monitor.h index 7cac9a8b2..ae14c101c 100644 --- a/monitor.h +++ b/src/track/monitor.h @@ -1,101 +1,104 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef MONITOR_H -#define MONITOR_H - -#include "std.h" - -#include "win32/win_thread.h" - -#define WM_MONITORCALLBACK (WM_APP + 0x32) -#define MONITOR_BUFFER_SIZE 4096 - -class FolderMonitor; - -// ============================================================================= - -enum FolderMonitorState { - MONITOR_STATE_STOPPED, - MONITOR_STATE_ACTIVE -}; - -class FolderInfo { -public: - FolderInfo(); - virtual ~FolderInfo(); - - friend class FolderMonitor; - -public: - wstring path; - int state; - - class FolderChangeInfo { - public: - FolderChangeInfo() : anime_id(0) {} - DWORD action; - int anime_id; - wstring file_name; - }; - vector change_list; - -private: - BYTE buffer_[MONITOR_BUFFER_SIZE]; - DWORD bytes_returned_; - HANDLE directory_handle_; - DWORD notify_filter_; - OVERLAPPED overlapped_; - BOOL watch_subtree_; -}; - -// ============================================================================= - -class FolderMonitor : public win32::Thread { -public: - FolderMonitor(); - virtual ~FolderMonitor(); - - // Worker thread - DWORD ThreadProc(); - - // Main thread - void Enable(bool enabled = true); - virtual void OnChange(FolderInfo* folder_info); - - HANDLE GetCompletionPort() { return completion_port_; } - void SetWindowHandle(HWND hwnd) { window_handle_ = hwnd; } - - bool AddFolder(const wstring& folder); - bool ClearFolders(); - bool Start(); - bool Stop(); - -private: - BOOL ReadDirectoryChanges(FolderInfo* folder_info); - -private: - win32::CriticalSection critical_section_; - vector folders_; - HANDLE completion_port_; - HWND window_handle_; -}; - -extern FolderMonitor FolderMonitor; - -#endif // MONITOR_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TRACK_MONITOR_H +#define TAIGA_TRACK_MONITOR_H + +#include +#include + +#include "win/win_thread.h" + +#define WM_MONITORCALLBACK (WM_APP + 0x32) +#define MONITOR_BUFFER_SIZE 4096 + +class FolderMonitor; + +enum FolderMonitorState { + kFolderMonitorStateStopped, + kFolderMonitorStateActive +}; + +enum PathType { + kPathTypeFile, + kPathTypeDirectory +}; + +class FolderInfo { +public: + FolderInfo(); + ~FolderInfo() {} + + friend class FolderMonitor; + + std::wstring path; + int state; + + class FolderChangeInfo { + public: + FolderChangeInfo(); + DWORD action; + std::wstring file_name; + LPARAM parameter; + PathType type; + }; + std::vector change_list; + +private: + BYTE buffer_[MONITOR_BUFFER_SIZE]; + DWORD bytes_returned_; + HANDLE directory_handle_; + DWORD notify_filter_; + OVERLAPPED overlapped_; + BOOL watch_subtree_; +}; + +class FolderMonitor : public win::Thread { +public: + FolderMonitor(); + virtual ~FolderMonitor(); + + // Worker thread + DWORD ThreadProc(); + + // Main thread + void Enable(bool enabled = true); + virtual void OnChange(FolderInfo& folder_info); + + HANDLE GetCompletionPort() const; + void SetWindowHandle(HWND hwnd); + + bool AddFolder(const std::wstring& folder); + bool ClearFolders(); + bool Start(); + void Stop(); + +private: + void HandleAnime(const std::wstring& path, FolderInfo& folder_info, size_t change_index); + bool IsPathAvailable(DWORD action) const; + BOOL ReadDirectoryChanges(FolderInfo& folder_info) const; + + win::CriticalSection critical_section_; + std::vector folders_; + HANDLE completion_port_; + HWND window_handle_; +}; + +extern FolderMonitor FolderMonitor; + +#endif // TAIGA_TRACK_MONITOR_H \ No newline at end of file diff --git a/recognition.cpp b/src/track/recognition.cpp similarity index 59% rename from recognition.cpp rename to src/track/recognition.cpp index f624f7f9f..a6b0ac0ef 100644 --- a/recognition.cpp +++ b/src/track/recognition.cpp @@ -1,827 +1,893 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "recognition.h" - -#include "anime_db.h" -#include "anime_episode.h" -#include "common.h" -#include "media.h" -#include "myanimelist.h" -#include "resource.h" -#include "settings.h" -#include "string.h" - -RecognitionEngine Meow; - -class Token { -public: - Token() : encloser('\0'), separator('\0'), virgin(true) {} - wchar_t encloser, separator; - wstring content; - bool virgin; -}; - -// ============================================================================= - -RecognitionEngine::RecognitionEngine() { - // Load keywords into memory - ReadKeyword(IDS_KEYWORD_AUDIO, audio_keywords); - ReadKeyword(IDS_KEYWORD_VIDEO, video_keywords); - ReadKeyword(IDS_KEYWORD_EXTRA, extra_keywords); - ReadKeyword(IDS_KEYWORD_EXTRA_UNSAFE, extra_unsafe_keywords); - ReadKeyword(IDS_KEYWORD_VERSION, version_keywords); - ReadKeyword(IDS_KEYWORD_EXTENSION, valid_extensions); - ReadKeyword(IDS_KEYWORD_EPISODE, episode_keywords); - ReadKeyword(IDS_KEYWORD_EPISODE_PREFIX, episode_prefixes); -} - -// ============================================================================= - -anime::Item* RecognitionEngine::MatchDatabase(anime::Episode& episode, - bool in_list, bool reverse, - bool strict, bool check_episode, - bool check_date, bool give_score) { - for (auto it = scores.begin(); it != scores.end(); ++it) { - it->second = 0; - } - - if (reverse) { - for (auto it = AnimeDatabase.items.rbegin(); it != AnimeDatabase.items.rend(); ++it) { - if (in_list && !it->second.IsInList()) - continue; - if (Meow.CompareEpisode(episode, it->second, strict, check_episode, check_date, give_score)) - return AnimeDatabase.FindItem(episode.anime_id); - } - } else { - for (auto it = AnimeDatabase.items.begin(); it != AnimeDatabase.items.end(); ++it) { - if (in_list && !it->second.IsInList()) - continue; - if (Meow.CompareEpisode(episode, it->second, strict, check_episode, check_date, give_score)) - return AnimeDatabase.FindItem(episode.anime_id); - } - } - - return nullptr; -} - -// ============================================================================= - -bool RecognitionEngine::CompareEpisode(anime::Episode& episode, - const anime::Item& anime_item, - bool strict, - bool check_episode, - bool check_date, - bool give_score) { - // Leave if title is empty - if (episode.clean_title.empty()) return false; - - // Leave if not yet aired - if (check_date && !anime_item.IsAiredYet()) return false; - - // Compare with titles - bool found = false; - - if (clean_titles[anime_item.GetId()].empty()) - UpdateCleanTitles(anime_item.GetId()); - for (auto it = clean_titles[anime_item.GetId()].begin(); - !found && it != clean_titles[anime_item.GetId()].end(); ++it) { - found = CompareTitle(*it, episode, anime_item, strict); - } - - if (!found) { - // Score title in case we need it later on - if (give_score) - ScoreTitle(episode, anime_item); - // Leave if not found - return false; - } - - // Validate episode number - if (check_episode && anime_item.GetEpisodeCount() > 0) { - int number = GetEpisodeHigh(episode.number); - if (number > anime_item.GetEpisodeCount()) { - // Check sequels - auto sequel = &anime_item; - do { - number -= sequel->GetEpisodeCount(); - sequel = AnimeDatabase.FindSequel(sequel->GetId()); - } while (sequel && number > sequel->GetEpisodeCount()); - if (sequel) { - episode.anime_id = sequel->GetId(); - episode.number = ToWstr(number); - return true; - } - // Episode number is out of range - return false; - } - } - // Assume episode 1 if matched one-episode series - if (episode.number.empty() && anime_item.GetEpisodeCount() == 1) - episode.number = L"1"; - - episode.anime_id = anime_item.GetId(); - return true; -} - -bool RecognitionEngine::CompareTitle(const wstring& anime_title, - anime::Episode& episode, - const anime::Item& anime_item, - bool strict) { - // Compare with title + number - if (strict && anime_item.GetEpisodeCount() == 1 && !episode.number.empty()) { - if (IsEqual(episode.clean_title + episode.number, anime_title)) { - episode.title += episode.number; - episode.number.clear(); - return true; - } - } - // Compare with title - if (strict) { - if (IsEqual(anime_title, episode.clean_title)) return true; - } else { - if (InStr(anime_title, episode.clean_title, 0, true) > -1) return true; - } - - // Failed - return false; -} - -std::multimap> RecognitionEngine::GetScores() { - std::multimap> reverse_map; - - for (auto it = scores.begin(); it != scores.end(); ++it) { - if (it->second == 0) continue; - reverse_map.insert(std::pair(it->second, it->first)); - } - - return reverse_map; -} - -bool RecognitionEngine::ScoreTitle(const anime::Episode& episode, const anime::Item& anime_item) { - const wstring& episode_title = episode.clean_title; - const wstring& anime_title = clean_titles[anime_item.GetId()].front(); - - const int score_bonus_small = 1; - const int score_bonus_big = 5; - const int score_min = std::abs(static_cast(episode_title.length()) - - static_cast(anime_title.length())); - const int score_max = episode_title.length() + anime_title.length(); - - int score = score_max; - - score -= LevenshteinDistance(episode_title, anime_title); - - score += LongestCommonSubsequenceLength(episode_title, anime_title) * 2; - score += LongestCommonSubstringLength(episode_title, anime_title) * 4; - - if (score <= score_min) - return false; - - if (anime_item.IsInList()) { - score += score_bonus_big; - switch (anime_item.GetMyStatus()) { - case mal::MYSTATUS_WATCHING: - case mal::MYSTATUS_PLANTOWATCH: - score += score_bonus_small; - break; - } - } - switch (anime_item.GetType()) { - case mal::TYPE_TV: - score += score_bonus_small; - break; - } - if (!episode.year.empty()) { - if (anime_item.GetDate(anime::DATE_START).year == ToInt(episode.year)) { - score += score_bonus_big; - } - } - - if (score > score_min) { - scores[anime_item.GetId()] = score; - return true; - } - - return false; -} - -// ============================================================================= - -bool RecognitionEngine::ExamineTitle(wstring title, anime::Episode& episode, - bool examine_inside, bool examine_outside, bool examine_number, - bool check_extras, bool check_extension) { - // Clear previous data - episode.Clear(); - if (title.empty()) return false; - - // Remove zero width space character - EraseChars(title, L"\u200B"); - - // Retrieve file name from full path - if (title.length() > 2 && title.at(1) == ':' && title.at(2) == '\\') { - episode.folder = GetPathOnly(title); - title = GetFileName(title); - } - episode.file = title; - - // Ignore if the file is outside of root folders - if (Settings.Account.Update.out_of_root) - if (!episode.folder.empty() && !Settings.Folders.root.empty()) - if (!anime::IsInsideRootFolders(episode.folder)) - return false; - - // Check and trim file extension - wstring extension = GetFileExtension(title); - if (!extension.empty() && extension.length() < title.length() && extension.length() <= 5) { - if (IsAlphanumeric(extension) && CheckFileExtension(extension, valid_extensions)) { - episode.format = ToUpper_Copy(extension); - title.resize(title.length() - extension.length() - 1); - } else { - if (IsNumeric(extension)) { - wstring temp = title.substr(0, title.length() - extension.length()); - for (auto it = episode_keywords.begin(); it != episode_keywords.end(); ++it) { - if (temp.length() >= it->length() && IsEqual(CharRight(temp, it->length()), *it)) { - title.resize(title.length() - extension.length() - it->length() - 1); - episode.number = extension; - break; - } - } - } - if (check_extension && episode.number.empty()) { - return false; - } - } - } - - ////////////////////////////////////////////////////////////////////////////// - // Here we are, entering the world of tokens. Each token has four properties: - // * content: Self-explanatory - // * encloser: Can be empty or one of these characters: [](){} - // * separator: The most common non-alphanumeric character - usually a space - // or an underscore - // * virgin: All tokens start out as virgins but lose this property when some - // keyword within is recognized and erased. - - // Tokenize - vector tokens; - tokens.reserve(4); - TokenizeTitle(title, L"[](){}", tokens); - if (tokens.empty()) return false; - title.clear(); - - // Examine tokens - for (unsigned int i = 0; i < tokens.size(); i++) { - if (IsTokenEnclosed(tokens[i])) { - if (examine_inside) ExamineToken(tokens[i], episode, check_extras); - } else { - if (examine_outside) ExamineToken(tokens[i], episode, check_extras); - } - } - - // Tidy up tokens - for (unsigned int i = 1; i < tokens.size() - 1; i++) { - // Combine remaining tokens that are enclosed with parentheses - this is - // especially useful for titles that include a year value and some other cases - if (tokens[i - 1].virgin == false || tokens[i].virgin == false || - IsTokenEnclosed(tokens[i - 1]) == true || tokens[i].encloser != '(' || - tokens[i - 1].content.length() < 2) { - continue; - } - tokens[i - 1].content += L"(" + tokens[i].content + L")"; - if (IsTokenEnclosed(tokens[i + 1]) == false) { - tokens[i - 1].content += tokens[i + 1].content; - if (tokens[i - 1].separator == '\0') - tokens[i - 1].separator = tokens[i + 1].separator; - tokens.erase(tokens.begin() + i + 1); - } - tokens.erase(tokens.begin() + i); - i = 0; - } - for (unsigned int i = 0; i < tokens.size(); i++) { - // Trim separator character from each side of the token - wchar_t trim_char[] = {tokens[i].separator, '\0'}; - Trim(tokens[i].content, trim_char); - // Tokens that are too short are now garbage, so we take them out - if (tokens[i].content.length() < 2 && !IsNumeric(tokens[i].content)) { - tokens.erase(tokens.begin() + i); - i--; - } - } - - ////////////////////////////////////////////////////////////////////////////// - // Now we apply some logic to decide on the title and the group name - - int group_index = -1, title_index = -1; - vector group_vector, title_vector; - for (unsigned int i = 0; i < tokens.size(); i++) { - if (IsTokenEnclosed(tokens[i])) { - group_vector.push_back(i); - } else { - title_vector.push_back(i); - } - } - - // Choose the first free token as the title, if there is one - if (title_vector.size() > 0) { - title_index = title_vector[0]; - title_vector.erase(title_vector.begin()); - } else { - // Choose the second enclosed token as the title, if there is more than one - // (it is more probable that the group name comes before the title) - if (group_vector.size() > 1) { - title_index = group_vector[1]; - group_vector.erase(group_vector.begin() + 1); - // Choose the first enclosed token as the title, if there is one - // (which means that there is no group name available) - } else if (group_vector.size() > 0) { - title_index = group_vector[0]; - group_vector.erase(group_vector.begin()); - } - } - - // Choose the first enclosed virgin token as the group name - for (unsigned int i = 0; i < group_vector.size(); i++) { - // Here we assume that group names are never enclosed with other keywords - if (tokens[group_vector[i]].virgin) { - group_index = group_vector[i]; - group_vector.erase(group_vector.begin() + i); - break; - } - } - // Group name might not be enclosed at all - // This is a special case for THORA releases, where the group name is at the end - if (group_index == -1) { - if (title_vector.size() > 0) { - group_index = title_vector.back(); - title_vector.erase(title_vector.end() - 1); - } - } - - // Do we have a title? - if (title_index > -1) { - // Replace the separator with a space character - ReplaceChar(tokens[title_index].content, tokens[title_index].separator, ' '); - // Do some clean-up - Trim(tokens[title_index].content, L" -"); - // Set the title - title = tokens[title_index].content; - tokens[title_index].content.clear(); - } - - // Do we have a group name? - if (group_index > -1) { - // We don't want to lose any character if the token is enclosed, because - // those characters can be a part of the group name itself - if (!IsTokenEnclosed(tokens[group_index])) { - // Replace the separator with a space character - ReplaceChar(tokens[group_index].content, tokens[group_index].separator, ' '); - // Do some clean-up - Trim(tokens[group_index].content, L" -"); - } - // Set the group name - episode.group = tokens[group_index].content; - // We don't clear token content here, becuse we'll be checking it for - // episode number later on - } - - ////////////////////////////////////////////////////////////////////////////// - // Get episode number and version, if available - - if (examine_number) { - // Check remaining tokens first - for (unsigned int i = 0; i < tokens.size(); i++) { - if (IsEpisodeFormat(tokens[i].content, episode, tokens[i].separator)) { - tokens[i].virgin = false; - break; - } - } - - // Check title - if (episode.number.empty()) { - // Split into words - vector words; - words.reserve(4); - Tokenize(title, L" ", words); - if (words.empty()) return false; - title.clear(); - int number_index = -1; - - // Check for episode number format, starting with the second word - for (unsigned int i = 1; i < words.size(); i++) { - if (IsEpisodeFormat(words[i], episode)) { - number_index = i; - break; - } - } - - // Set the first valid numeric token as episode number - if (episode.number.empty()) { - for (unsigned int i = 0; i < tokens.size(); i++) { - if (IsNumeric(tokens[i].content)) { - episode.number = tokens[i].content; - if (ValidateEpisodeNumber(episode)) { - tokens[i].virgin = false; - break; - } - } - } - } - - // Set the lastmost number that follows a '-' - if (episode.number.empty() && words.size() > 2) { - for (unsigned int i = words.size() - 2; i > 0; i--) { - if (words[i] == L"-" && IsNumeric(words[i + 1])) { - episode.number = words[i + 1]; - if (ValidateEpisodeNumber(episode)) { - number_index = i + 1; - break; - } - } - } - } - - // Set the lastmost number as a last resort - if (episode.number.empty()) { - for (unsigned int i = words.size() - 1; i > 0; i--) { - if (IsNumeric(words[i])) { - episode.number = words[i]; - if (ValidateEpisodeNumber(episode)) { - // Discard and give up if movie or season number (episode numbers cannot precede them) - if (i > 1 && (IsEqual(words[i - 1], L"Season") || IsEqual(words[i - 1], L"Movie")) && - !IsCountingWord(words[i - 2])) { - episode.number.clear(); - number_index = -1; - break; - } - - number_index = i; - break; - } - } - } - } - - // Build title and name - for (unsigned int i = 0; i < words.size(); i++) { - if (i < number_index) { - // Ignore episode keywords - if (i == number_index - 1 && CompareKeys(words[i], episode_keywords)) continue; - AppendKeyword(title, words[i]); - } else if (i > number_index) { - AppendKeyword(episode.name, words[i]); - } - } - - // Clean up - TrimRight(title, L" -"); - TrimLeft(episode.name, L" -"); - if (StartsWith(episode.name, L"'") && EndsWith(episode.name, L"'")) - Trim(episode.name, L"'"); - TrimLeft(episode.name, L"\u300C"); // Japanese left quotation mark - TrimRight(episode.name, L"\u300D"); // Japanese right quotation mark - } - } - - ////////////////////////////////////////////////////////////////////////////// - - // Check if the group token is still a virgin - if (group_index > -1 && !tokens[group_index].virgin) { - episode.group.clear(); - for (unsigned int i = 0; i < tokens.size(); i++) { - // Set the first available virgin token as group name - if (!tokens[i].content.empty() && tokens[i].virgin) { - episode.group = tokens[i].content; - break; - } - } - } - // Set episode name as group name if necessary - if (episode.group.empty() && episode.name.length() > 2) { - if (StartsWith(episode.name, L"(") && EndsWith(episode.name, L")")) { - episode.group = episode.name.substr(1, episode.name.length() - 2); - episode.name.clear(); - } - } - - // Examine remaining tokens once more - for (unsigned int i = 0; i < tokens.size(); i++) { - if (!tokens[i].content.empty()) { - ExamineToken(tokens[i], episode, true); - } - } - - ////////////////////////////////////////////////////////////////////////////// - - // Set the final title, hopefully name of the anime - episode.title = title; - episode.clean_title = title; - CleanTitle(episode.clean_title); - - return !title.empty(); -} - -// ============================================================================= - -void RecognitionEngine::ExamineToken(Token& token, anime::Episode& episode, bool compare_extras) { - // Split into words. The most common non-alphanumeric character is the - // separator. - vector words; - token.separator = GetMostCommonCharacter(token.content); - Split(token.content, wstring(1, token.separator), words); - - // Revert if there are words that are too short. This prevents splitting some - // group names (e.g. "m.3.3.w") and keywords (e.g. "H.264"). - if (IsTokenEnclosed(token)) { - for (unsigned int i = 0; i < words.size(); i++) { - if (words[i].length() == 1) { - words.clear(); words.push_back(token.content); break; - } - } - } - - // Compare with keywords - for (unsigned int i = 0; i < words.size(); i++) { - Trim(words[i]); - if (words[i].empty()) continue; - #define RemoveWordFromToken(b) { \ - Erase(token.content, words[i], b); token.virgin = false; } - - // Checksum - if (episode.checksum.empty() && words[i].length() == 8 && IsHex(words[i])) { - episode.checksum = words[i]; - RemoveWordFromToken(false); - // Video resolution - } else if (episode.resolution.empty() && IsResolution(words[i])) { - episode.resolution = words[i]; - RemoveWordFromToken(false); - // Video info - } else if (CompareKeys(words[i], video_keywords)) { - AppendKeyword(episode.video_type, words[i]); - RemoveWordFromToken(true); - // Audio info - } else if (CompareKeys(words[i], audio_keywords)) { - AppendKeyword(episode.audio_type, words[i]); - RemoveWordFromToken(true); - // Version - } else if (episode.version.empty() && CompareKeys(words[i], version_keywords)) { - episode.version.push_back(words[i].at(words[i].length() - 1)); - RemoveWordFromToken(true); - // Extras - } else if (compare_extras && CompareKeys(words[i], extra_keywords)) { - AppendKeyword(episode.extras, words[i]); - RemoveWordFromToken(true); - } else if (compare_extras && CompareKeys(words[i], extra_unsafe_keywords)) { - AppendKeyword(episode.extras, words[i]); - if (IsTokenEnclosed(token)) RemoveWordFromToken(true); - } - - #undef RemoveWordFromToken - } -} - -// ============================================================================= - -// Helper functions - -void RecognitionEngine::AppendKeyword(wstring& str, const wstring& keyword) { - AppendString(str, keyword, L" "); -} - -bool RecognitionEngine::CompareKeys(const wstring& str, const vector& keys) { - if (!str.empty()) - for (unsigned int i = 0; i < keys.size(); i++) - if (IsEqual(str, keys[i])) - return true; - return false; -} - -void RecognitionEngine::CleanTitle(wstring& title) { - if (title.empty()) return; - EraseUnnecessary(title); - TransliterateSpecial(title); - ErasePunctuation(title, true); -} - -void RecognitionEngine::UpdateCleanTitles(int anime_id) { - auto anime_item = AnimeDatabase.FindItem(anime_id); - - clean_titles[anime_id].clear(); - - // Main title - clean_titles[anime_id].push_back(anime_item->GetTitle()); - CleanTitle(clean_titles[anime_id].back()); - - // English title - if (!anime_item->GetEnglishTitle().empty()) { - clean_titles[anime_id].push_back(anime_item->GetEnglishTitle()); - CleanTitle(clean_titles[anime_id].back()); - } - - // Synonyms - if (!anime_item->GetUserSynonyms().empty()) { - for (auto it = anime_item->GetUserSynonyms().begin(); - it != anime_item->GetUserSynonyms().end(); ++it) { - clean_titles[anime_id].push_back(*it); - CleanTitle(clean_titles[anime_id].back()); - } - } - if (!anime_item->GetSynonyms().empty()) { - for (auto it = anime_item->GetSynonyms().begin(); - it != anime_item->GetSynonyms().end(); ++it) { - clean_titles[anime_id].push_back(*it); - CleanTitle(clean_titles[anime_id].back()); - } - } -} - -void RecognitionEngine::EraseUnnecessary(wstring& str) { - EraseLeft(str, L"the ", true); - Replace(str, L" the ", L" ", false, true); - Erase(str, L"episode ", true); - Erase(str, L" ep.", true); - Replace(str, L" specials", L" special", false, true); -} - -// TODO: make faster -void RecognitionEngine::TransliterateSpecial(wstring& str) { - // Character equivalencies - ReplaceChar(str, L'\u00E9', L'e'); // small e acute accent - ReplaceChar(str, L'\uFF0F', L'/'); // unicode slash - ReplaceChar(str, L'\uFF5E', L'~'); // unicode tilde - ReplaceChar(str, L'\u223C', L'~'); // unicode tilde 2 - ReplaceChar(str, L'\u301C', L'~'); // unicode tilde 3 - ReplaceChar(str, L'\uFF1F', L'?'); // unicode question mark - ReplaceChar(str, L'\uFF01', L'!'); // unicode exclamation point - ReplaceChar(str, L'\u00D7', L'x'); // multiplication symbol - ReplaceChar(str, L'\u2715', L'x'); // multiplication symbol 2 - - // A few common always-equivalent romanizations - Replace(str, L"\u014C", L"Ou"); // O macron - Replace(str, L"\u014D", L"ou"); // o macron - Replace(str, L"\u016B", L"uu"); // u macron - Replace(str, L" wa ", L" ha "); // hepburn to wapuro - Replace(str, L" e ", L" he "); // hepburn to wapuro - Replace(str, L" o ", L" wo "); // hepburn to wapuro - - // Abbreviations - Replace(str, L" & ", L" and ", true, false); -} - -bool RecognitionEngine::IsEpisodeFormat(const wstring& str, anime::Episode& episode, const wchar_t separator) { - unsigned int numstart, i, j; - - // Find first number - for (numstart = 0; numstart < str.length() && !IsNumeric(str.at(numstart)); numstart++); - if (numstart == str.length()) return false; - - // Check for episode prefix - if (numstart > 0) { - if (!CompareKeys(str.substr(0, numstart), episode_prefixes)) return false; - } - - for (i = numstart + 1; i < str.length(); i++) { - if (!IsNumeric(str.at(i))) { - // *#-#* - if (str.at(i) == '-' || str.at(i) == '&') { - if (i == str.length() - 1 || !IsNumeric(str.at(i + 1))) return false; - for (j = i + 1; j < str.length() && IsNumeric(str.at(j)); j++); - episode.number = str.substr(numstart, j - numstart); - // *#-#*v# - if (j < str.length() - 1 && (str.at(j) == 'v' || str.at(j) =='V')) { - numstart = i + 1; - continue; - } else { - return true; - } - - // v# - } else if (str.at(i) == 'v' || str.at(i) == 'V') { - if (episode.number.empty()) { - if (i == str.length() - 1 || !IsNumeric(str.at(i + 1))) return false; - episode.number = str.substr(numstart, i - numstart); - } - episode.version = str.substr(i + 1); - return true; - - // *# of #* - } else if (str.at(i) == separator) { - if (str.length() < i + 5) return false; - if (str.at(i + 1) == 'o' && - str.at(i + 2) == 'f' && - str.at(i + 3) == separator) { - episode.number = str.substr(numstart, i - numstart); - return true; - } - - // *#x#* - } else if (str.at(i) == 'x') { - if (i == str.length() - 1 || !IsNumeric(str.at(i + 1))) return false; - for (j = i + 1; j < str.length() && IsNumeric(str.at(j)); j++); - episode.number = str.substr(i + 1, j - i - 1); - if (ToInt(episode.number) < 100) { - return true; - } else { - episode.number.clear(); - return false; - } - - // Japanese counter - } else if (str.at(i) == L'\u8A71') { - episode.number = str.substr(numstart, i - 1); - return true; - - } else { - episode.number.clear(); - return false; - } - } - } - - // [prefix]#* - if (numstart > 0 && episode.number.empty()) { - episode.number = str.substr(numstart, str.length() - numstart); - if (!ValidateEpisodeNumber(episode)) return false; - return true; - } - - // *# - return false; -} - -bool RecognitionEngine::IsResolution(const wstring& str) { - return TranslateResolution(str, true) > 0; -} - -bool RecognitionEngine::IsCountingWord(const wstring& str) { - if (str.length() > 2) { - if (EndsWith(str, L"th") || EndsWith(str, L"nd") || EndsWith(str, L"rd") || EndsWith(str, L"st") || - EndsWith(str, L"TH") || EndsWith(str, L"ND") || EndsWith(str, L"RD") || EndsWith(str, L"ST")) { - if (IsNumeric(str.substr(0, str.length() - 2)) || - IsEqual(str, L"FIRST") || - IsEqual(str, L"SECOND") || - IsEqual(str, L"THIRD") || - IsEqual(str, L"FOURTH") || - IsEqual(str, L"FIFTH")) { - return true; - } - } - } - return false; -} - -bool RecognitionEngine::IsTokenEnclosed(const Token& token) { - return token.encloser == '[' || token.encloser == '(' || token.encloser == '{'; -} - -void RecognitionEngine::ReadKeyword(unsigned int id, vector& str) { - wstring str_buff; - ReadStringTable(id, str_buff); - Split(str_buff, L", ", str); -} - -size_t RecognitionEngine::TokenizeTitle(const wstring& str, const wstring& delimiters, vector& tokens) { - size_t index_begin = str.find_first_not_of(delimiters); - while (index_begin != wstring::npos) { - size_t index_end = str.find_first_of(delimiters, index_begin + 1); - tokens.resize(tokens.size() + 1); - if (index_end == wstring::npos) { - tokens.back().content = str.substr(index_begin); - break; - } else { - tokens.back().content = str.substr(index_begin, index_end - index_begin); - if (index_begin > 0) tokens.back().encloser = str.at(index_begin - 1); - index_begin = str.find_first_not_of(delimiters, index_end + 1); - } - } - return tokens.size(); -} - -bool RecognitionEngine::ValidateEpisodeNumber(anime::Episode& episode) { - int number = ToInt(episode.number); - if (number <= 0 || number > 1000) { - if (number > 1950 && number < 2050) { - episode.year = episode.number; - } - episode.number.clear(); - return false; - } - return true; +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/anime_episode.h" +#include "library/anime_util.h" +#include "taiga/resource.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" +#include "track/media.h" +#include "track/recognition.h" + +RecognitionEngine Meow; + +class Token { +public: + Token() : encloser('\0'), separator('\0'), untouched(true) {} + + std::wstring content; + wchar_t encloser; + wchar_t separator; + bool untouched; +}; + +RecognitionEngine::RecognitionEngine() { + ReadKeyword(audio_keywords, + L"2CH, 5.1CH, 5.1, AAC, AC3, DTS, DTS5.1, DTS-ES, DUALAUDIO, DUAL AUDIO, " + L"FLAC, MP3, OGG, TRUEHD5.1, VORBIS"); + ReadKeyword(video_keywords, + L"8BIT, 8-BIT, 10BIT, 10-BIT, AVI, DIVX, H264, H.264, HD, HDTV, HI10P, " + L"HQ, LQ, RMVB, SD, TS, VFR, WMV, X264, X.264, XVID"); + ReadKeyword(extra_keywords, + L"ASS, BATCH, BD, BLURAY, BLU-RAY, COMPLETE, DIRECTOR'S CUT, DVD, DVD5, " + L"DVD9, DVD-R2J, DVDRIP, ENG, ENGLISH, HARDSUB, PS3, R2DVD, R2J, R2JDVD, " + L"RAW, REMASTERED, SOFTSUB, SUBBED, SUB, UNCENSORED, UNCUT, VOSTFR, " + L"WEBCAST, WIDESCREEN, WS"); + ReadKeyword(extra_unsafe_keywords, + L"END, FINAL, OAV, ONA, OVA"); + ReadKeyword(version_keywords, + L"V0, V2, V3, V4"); + ReadKeyword(valid_extensions, + L"MKV, AVI, MP4, OGM, RM, RMVB, WMV, DIVX, MOV, FLV, MPG, 3GP"); + ReadKeyword(episode_keywords, + L"EPISODE, EP., EP, VOLUME, VOL., VOL, EPS., EPS"); + ReadKeyword(episode_prefixes, + L"EP., EP, E, VOL., VOL, EPS., \x7B2C"); +} + +//////////////////////////////////////////////////////////////////////////////// + +anime::Item* RecognitionEngine::MatchDatabase(anime::Episode& episode, + bool in_list, + bool reverse, + bool strict, + bool check_episode, + bool check_date, + bool give_score) { + // Reset scores + foreach_(it, scores) + it->second = 0; + + if (reverse) { + foreach_r_(it, AnimeDatabase.items) { + if (in_list && !it->second.IsInList()) + continue; + if (Meow.CompareEpisode(episode, it->second, strict, check_episode, + check_date, give_score)) + return AnimeDatabase.FindItem(episode.anime_id); + } + } else { + foreach_(it, AnimeDatabase.items) { + if (in_list && !it->second.IsInList()) + continue; + if (Meow.CompareEpisode(episode, it->second, strict, check_episode, + check_date, give_score)) + return AnimeDatabase.FindItem(episode.anime_id); + } + } + + return nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool RecognitionEngine::CompareEpisode(anime::Episode& episode, + const anime::Item& anime_item, + bool strict, + bool check_episode, + bool check_date, + bool give_score) { + // Leave if title is empty + if (episode.clean_title.empty()) + return false; + + // Leave if not yet aired + if (check_date && !anime::IsAiredYet(anime_item)) + return false; + + bool found = false; + + // Compare with titles + if (clean_titles[anime_item.GetId()].empty()) + UpdateCleanTitles(anime_item.GetId()); + foreach_(it, clean_titles[anime_item.GetId()]) { + found = CompareTitle(*it, episode, anime_item, strict); + if (found) + break; + } + + if (!found) { + // Score title in case we need it later on + if (give_score) + ScoreTitle(episode, anime_item); + // Leave if not found + return false; + } + + // Validate episode number + if (check_episode && anime_item.GetEpisodeCount() > 0) { + int number = anime::GetEpisodeHigh(episode.number); + if (number > anime_item.GetEpisodeCount()) { + // Check sequels + auto sequel = &anime_item; + do { + number -= sequel->GetEpisodeCount(); + sequel = AnimeDatabase.FindSequel(sequel->GetId()); + } while (sequel && number > sequel->GetEpisodeCount()); + if (sequel) { + episode.anime_id = sequel->GetId(); + episode.number = ToWstr(number); + return true; + } + // Episode number is out of range + return false; + } + } + // Assume episode 1 if matched one-episode series + if (episode.number.empty() && anime_item.GetEpisodeCount() == 1) + episode.number = L"1"; + + episode.anime_id = anime_item.GetId(); + + return true; +} + +bool RecognitionEngine::CompareTitle(const std::wstring& anime_title, + anime::Episode& episode, + const anime::Item& anime_item, + bool strict) { + // Compare with title + number + if (strict && anime_item.GetEpisodeCount() == 1 && !episode.number.empty()) { + if (IsEqual(episode.clean_title + episode.number, anime_title)) { + episode.title += episode.number; + episode.number.clear(); + return true; + } + } + // Compare with title + if (strict) { + if (IsEqual(anime_title, episode.clean_title)) + return true; + } else { + if (InStr(anime_title, episode.clean_title, 0, true) > -1) + return true; + } + + return false; +} + +std::multimap> RecognitionEngine::GetScores() { + std::multimap> reverse_map; + + foreach_(it, scores) { + if (it->second == 0) + continue; + reverse_map.insert(std::pair(it->second, it->first)); + } + + return reverse_map; +} + +bool RecognitionEngine::ScoreTitle(const anime::Episode& episode, + const anime::Item& anime_item) { + const std::wstring& episode_title = episode.clean_title; + const std::wstring& anime_title = clean_titles[anime_item.GetId()].front(); + + const int score_bonus_small = 1; + const int score_bonus_big = 5; + const int score_min = std::abs(static_cast(episode_title.length()) - + static_cast(anime_title.length())); + const int score_max = episode_title.length() + anime_title.length(); + + int score = score_max; + + score -= LevenshteinDistance(episode_title, anime_title); + + score += LongestCommonSubsequenceLength(episode_title, anime_title) * 2; + score += LongestCommonSubstringLength(episode_title, anime_title) * 4; + + if (score <= score_min) + return false; + + if (anime_item.IsInList()) { + score += score_bonus_big; + switch (anime_item.GetMyStatus()) { + case anime::kWatching: + case anime::kPlanToWatch: + score += score_bonus_small; + break; + } + } + switch (anime_item.GetType()) { + case anime::kTv: + score += score_bonus_small; + break; + } + if (!episode.year.empty()) { + if (anime_item.GetDateStart().year == ToInt(episode.year)) { + score += score_bonus_big; + } + } + + if (score > score_min) { + scores[anime_item.GetId()] = score; + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool RecognitionEngine::ExamineTitle(std::wstring title, + anime::Episode& episode, + bool examine_inside, + bool examine_outside, + bool examine_number, + bool check_extras, + bool check_extension) { + // Clear previous data + episode.Clear(); + + if (title.empty()) + return false; + + // Remove zero width space character + EraseChars(title, L"\u200B"); // TEMP + + // Retrieve file name from full path + if (title.length() > 2 && title.at(1) == ':' && title.at(2) == '\\') { + episode.folder = GetPathOnly(title); + title = GetFileName(title); + } + episode.file = title; + + // Ignore if the file is outside of root folders + if (Settings.GetBool(taiga::kSync_Update_OutOfRoot)) + if (!episode.folder.empty() && !Settings.root_folders.empty()) + if (!anime::IsInsideRootFolders(episode.folder)) + return false; + + // Check and trim file extension + std::wstring extension = GetFileExtension(title); + if (!extension.empty() && + extension.length() < title.length() && + extension.length() <= 5) { + if (IsAlphanumeric(extension) && + CheckFileExtension(extension, valid_extensions)) { + episode.format = ToUpper_Copy(extension); + title.resize(title.length() - extension.length() - 1); + } else { + if (IsNumeric(extension)) { + std::wstring temp = title.substr(0, title.length() - extension.length()); + foreach_(it, episode_keywords) { + if (temp.length() >= it->length() && + IsEqual(CharRight(temp, it->length()), *it)) { + title.resize(title.length() - extension.length() - it->length() - 1); + episode.number = extension; + break; + } + } + } + if (check_extension && episode.number.empty()) + return false; + } + } + + ////////////////////////////////////////////////////////////////////////////// + // Here we are, entering the world of tokens. Each token has four properties: + // * content: Self-explanatory + // * encloser: Can be empty or one of these characters: [](){} + // * separator: The most common non-alphanumeric character - usually a space + // or an underscore + // * untouched: All tokens start out as untouched but lose this property when + // some keyword within is recognized and erased. + + // Tokenize + std::vector tokens; + tokens.reserve(4); + TokenizeTitle(title, L"[](){}", tokens); + if (tokens.empty()) + return false; + title.clear(); + + // Examine tokens + foreach_(token, tokens) { + if (IsTokenEnclosed(*token)) { + if (examine_inside) + ExamineToken(*token, episode, check_extras); + } else { + if (examine_outside) + ExamineToken(*token, episode, check_extras); + } + } + + // Tidy up tokens + for (size_t i = 1; i < tokens.size() - 1; i++) { + // Combine remaining tokens that are enclosed with parentheses - this is + // especially useful for titles that include a year value and some other cases + if (tokens[i - 1].untouched == false || + tokens[i].untouched == false || + IsTokenEnclosed(tokens[i - 1]) == true || + tokens[i].encloser != '(' || + tokens[i - 1].content.length() < 2) { + continue; + } + tokens[i - 1].content += L"(" + tokens[i].content + L")"; + if (IsTokenEnclosed(tokens[i + 1]) == false) { + tokens[i - 1].content += tokens[i + 1].content; + if (tokens[i - 1].separator == '\0') + tokens[i - 1].separator = tokens[i + 1].separator; + tokens.erase(tokens.begin() + i + 1); + } + tokens.erase(tokens.begin() + i); + i = 0; + } + for (size_t i = 0; i < tokens.size(); i++) { + // Trim separator character from each side of the token + wchar_t trim_char[] = {tokens[i].separator, '\0'}; + Trim(tokens[i].content, trim_char); + // Tokens that are too short are now garbage, so we take them out + if (tokens[i].content.length() < 2 && !IsNumeric(tokens[i].content)) { + tokens.erase(tokens.begin() + i); + i--; + } + } + + ////////////////////////////////////////////////////////////////////////////// + // Now we apply some logic to decide on the title and the group name + + int group_index = -1; + int title_index = -1; + std::vector group_vector; + std::vector title_vector; + for (size_t i = 0; i < tokens.size(); i++) { + if (IsTokenEnclosed(tokens[i])) { + group_vector.push_back(i); + } else { + title_vector.push_back(i); + } + } + + // Choose the first free token as the title, if there is one + if (title_vector.size() > 0) { + title_index = title_vector[0]; + title_vector.erase(title_vector.begin()); + } else { + // Choose the second enclosed token as the title, if there is more than one + // (it is more probable that the group name comes before the title) + if (group_vector.size() > 1) { + title_index = group_vector[1]; + group_vector.erase(group_vector.begin() + 1); + // Choose the first enclosed token as the title, if there is one + // (which means that there is no group name available) + } else if (group_vector.size() > 0) { + title_index = group_vector[0]; + group_vector.erase(group_vector.begin()); + } + } + + // Choose the first enclosed untouched token as the group name + for (size_t i = 0; i < group_vector.size(); i++) { + // Here we assume that group names are never enclosed with other keywords + if (tokens[group_vector[i]].untouched) { + group_index = group_vector[i]; + group_vector.erase(group_vector.begin() + i); + break; + } + } + // Group name might not be enclosed at all + // This is a special case for THORA releases, where the group name is at the end + if (group_index == -1) { + if (title_vector.size() > 0) { + group_index = title_vector.back(); + title_vector.erase(title_vector.end() - 1); + } + } + + // Do we have a title? + if (title_index > -1) { + // Replace the separator with a space character + ReplaceChar(tokens[title_index].content, tokens[title_index].separator, ' '); + // Do some clean-up + Trim(tokens[title_index].content, L" -"); + // Set the title + title = tokens[title_index].content; + tokens[title_index].content.clear(); + tokens[title_index].untouched = false; + } + + // Do we have a group name? + if (group_index > -1) { + // We don't want to lose any character if the token is enclosed, because + // those characters can be a part of the group name itself + if (!IsTokenEnclosed(tokens[group_index])) { + // Replace the separator with a space character + ReplaceChar(tokens[group_index].content, tokens[group_index].separator, ' '); + // Do some clean-up + Trim(tokens[group_index].content, L" -"); + } + // Set the group name + episode.group = tokens[group_index].content; + // We don't clear token content here, becuse we'll be checking it for + // episode number later on + } + + ////////////////////////////////////////////////////////////////////////////// + // Get episode number and version, if available + + if (examine_number) { + // Check remaining tokens first + foreach_(token, tokens) { + if (IsEpisodeFormat(token->content, episode, token->separator)) { + token->untouched = false; + break; + } + } + + // Check title + if (episode.number.empty()) { + // Split into words + std::vector words; + words.reserve(4); + Tokenize(title, L" ", words); + if (words.empty()) + return false; + title.clear(); + int number_index = -1; + + // Check for episode number format, starting with the second word + for (size_t i = 1; i < words.size(); i++) { + if (IsEpisodeFormat(words[i], episode)) { + number_index = static_cast(i); + break; + } + } + + // Set the first valid numeric token as episode number + if (episode.number.empty()) { + foreach_(token, tokens) { + if (IsNumeric(token->content)) { + episode.number = token->content; + if (ValidateEpisodeNumber(episode)) { + token->untouched = false; + break; + } + } + } + } + + // Set the lastmost number that follows a '-' + if (episode.number.empty() && words.size() > 2) { + for (size_t i = words.size() - 2; i > 0; i--) { + if (words[i] == L"-" && IsNumeric(words[i + 1])) { + episode.number = words[i + 1]; + if (ValidateEpisodeNumber(episode)) { + number_index = static_cast(i) + 1; + break; + } + } + } + } + + // Set the lastmost number as a last resort + if (episode.number.empty()) { + for (size_t i = words.size() - 1; i > 0; i--) { + if (IsNumeric(words[i])) { + episode.number = words[i]; + if (ValidateEpisodeNumber(episode)) { + // Discard and give up if movie or season number (episode numbers + // cannot precede them) + if (i > 1 && + (IsEqual(words[i - 1], L"Season") || + IsEqual(words[i - 1], L"Movie")) && + !IsCountingWord(words[i - 2])) { + episode.number.clear(); + number_index = -1; + break; + } + + number_index = static_cast(i); + break; + } + } + } + } + + // Build title and name + for (int i = 0; i < static_cast(words.size()); i++) { + if (number_index == -1 || i < number_index) { + // Ignore episode keywords + if (i == number_index - 1 && CompareKeys(words[i], episode_keywords)) + continue; + AppendKeyword(title, words[i]); + } else if (i > number_index) { + AppendKeyword(episode.name, words[i]); + } + } + + // Clean up + TrimRight(title, L" -"); + TrimLeft(episode.name, L" -"); + if (StartsWith(episode.name, L"'") && EndsWith(episode.name, L"'")) + Trim(episode.name, L"'"); + TrimLeft(episode.name, L"\u300C"); // Japanese left quotation mark + TrimRight(episode.name, L"\u300D"); // Japanese right quotation mark + } + } + + ////////////////////////////////////////////////////////////////////////////// + + // Check if the group token is still untouched + if (group_index > -1 && !tokens[group_index].untouched) { + episode.group.clear(); + foreach_(token, tokens) { + // Set the first available untouched token as group name + if (!token->content.empty() && token->untouched) { + episode.group = token->content; + break; + } + } + } + // Set episode name as group name if necessary + if (episode.group.empty() && episode.name.length() > 2) { + if (StartsWith(episode.name, L"(") && EndsWith(episode.name, L")")) { + episode.group = episode.name.substr(1, episode.name.length() - 2); + episode.name.clear(); + } + } + + // Examine remaining tokens once more + foreach_(token, tokens) + if (!token->content.empty()) + ExamineToken(*token, episode, true); + + ////////////////////////////////////////////////////////////////////////////// + + // Set the final title, hopefully name of the anime + episode.title = title; + episode.clean_title = title; + CleanTitle(episode.clean_title); + + return !title.empty(); +} + +//////////////////////////////////////////////////////////////////////////////// + +void RecognitionEngine::ExamineToken(Token& token, anime::Episode& episode, + bool compare_extras) { + // Split into words. The most common non-alphanumeric character is the + // separator. + std::vector words; + token.separator = GetMostCommonCharacter(token.content); + Split(token.content, std::wstring(1, token.separator), words); + + // Revert if there are words that are too short. This prevents splitting some + // group names (e.g. "m.3.3.w") and keywords (e.g. "H.264"). + if (IsTokenEnclosed(token)) { + foreach_(word, words) { + if (word->length() == 1) { + words.clear(); + words.push_back(token.content); + break; + } + } + } + + // Compare with keywords + foreach_(word, words) { + Trim(*word); + if (word->empty()) + continue; + #define RemoveWordFromToken(b) { \ + Erase(token.content, *word, b); token.untouched = false; } + + // Checksum + if (episode.checksum.empty() && word->length() == 8 && IsHex(*word)) { + episode.checksum = *word; + RemoveWordFromToken(false); + // Video resolution + } else if (episode.resolution.empty() && IsResolution(*word)) { + episode.resolution = *word; + RemoveWordFromToken(false); + // Video info + } else if (CompareKeys(*word, video_keywords)) { + AppendKeyword(episode.video_type, *word); + RemoveWordFromToken(true); + // Audio info + } else if (CompareKeys(*word, audio_keywords)) { + AppendKeyword(episode.audio_type, *word); + RemoveWordFromToken(true); + // Version + } else if (episode.version.empty() && CompareKeys(*word, version_keywords)) { + episode.version.push_back(word->at(word->length() - 1)); + RemoveWordFromToken(true); + // Extras + } else if (compare_extras && CompareKeys(*word, extra_keywords)) { + AppendKeyword(episode.extras, *word); + RemoveWordFromToken(true); + } else if (compare_extras && CompareKeys(*word, extra_unsafe_keywords)) { + AppendKeyword(episode.extras, *word); + if (IsTokenEnclosed(token)) + RemoveWordFromToken(true); + } + + #undef RemoveWordFromToken + } +} + +//////////////////////////////////////////////////////////////////////////////// + +// Helper functions + +void RecognitionEngine::AppendKeyword(std::wstring& str, + const std::wstring& keyword) { + AppendString(str, keyword, L" "); +} + +bool RecognitionEngine::CompareKeys(const std::wstring& str, + const std::vector& keys) { + if (!str.empty()) + foreach_(key, keys) + if (IsEqual(str, *key)) + return true; + + return false; +} + +void RecognitionEngine::CleanTitle(std::wstring& title) { + if (title.empty()) + return; + + EraseUnnecessary(title); + TransliterateSpecial(title); + ErasePunctuation(title, true); +} + +void RecognitionEngine::UpdateCleanTitles(int anime_id) { + auto anime_item = AnimeDatabase.FindItem(anime_id); + + clean_titles[anime_id].clear(); + + // Main title + clean_titles[anime_id].push_back(anime_item->GetTitle()); + CleanTitle(clean_titles[anime_id].back()); + + // English title + if (!anime_item->GetEnglishTitle().empty()) { + clean_titles[anime_id].push_back(anime_item->GetEnglishTitle()); + CleanTitle(clean_titles[anime_id].back()); + } + + // Synonyms + if (!anime_item->GetUserSynonyms().empty()) { + foreach_(it, anime_item->GetUserSynonyms()) { + clean_titles[anime_id].push_back(*it); + CleanTitle(clean_titles[anime_id].back()); + } + } + if (!anime_item->GetSynonyms().empty()) { + auto synonyms = anime_item->GetSynonyms(); + foreach_(it, synonyms) { + clean_titles[anime_id].push_back(*it); + CleanTitle(clean_titles[anime_id].back()); + } + } +} + +void RecognitionEngine::EraseUnnecessary(std::wstring& str) { + EraseLeft(str, L"the ", true); + Replace(str, L" the ", L" ", false, true); + Erase(str, L"episode ", true); + Erase(str, L" ep.", true); + Replace(str, L" specials", L" special", false, true); +} + +// TODO: make faster +void RecognitionEngine::TransliterateSpecial(std::wstring& str) { + // Character equivalencies + ReplaceChar(str, L'\u00E9', L'e'); // small e acute accent + ReplaceChar(str, L'\uFF0F', L'/'); // unicode slash + ReplaceChar(str, L'\uFF5E', L'~'); // unicode tilde + ReplaceChar(str, L'\u223C', L'~'); // unicode tilde 2 + ReplaceChar(str, L'\u301C', L'~'); // unicode tilde 3 + ReplaceChar(str, L'\uFF1F', L'?'); // unicode question mark + ReplaceChar(str, L'\uFF01', L'!'); // unicode exclamation point + ReplaceChar(str, L'\u00D7', L'x'); // multiplication symbol + ReplaceChar(str, L'\u2715', L'x'); // multiplication symbol 2 + + // A few common always-equivalent romanizations + Replace(str, L"\u014C", L"Ou"); // O macron + Replace(str, L"\u014D", L"ou"); // o macron + Replace(str, L"\u016B", L"uu"); // u macron + Replace(str, L" wa ", L" ha "); // hepburn to wapuro + Replace(str, L" e ", L" he "); // hepburn to wapuro + Replace(str, L" o ", L" wo "); // hepburn to wapuro + + // Abbreviations + Replace(str, L" & ", L" and ", true, false); +} + +bool RecognitionEngine::IsEpisodeFormat(const std::wstring& str, + anime::Episode& episode, + const wchar_t separator) { + unsigned int numstart, i, j; + + // Find first number + for (numstart = 0; numstart < str.length() && !IsNumeric(str.at(numstart)); numstart++); + if (numstart == str.length()) + return false; + + // Check for episode prefix + if (numstart > 0) + if (!CompareKeys(str.substr(0, numstart), episode_prefixes)) + return false; + + for (i = numstart + 1; i < str.length(); i++) { + if (!IsNumeric(str.at(i))) { + // *#-#* + if (str.at(i) == '-' || str.at(i) == '&') { + if (i == str.length() - 1 || !IsNumeric(str.at(i + 1))) + return false; + for (j = i + 1; j < str.length() && IsNumeric(str.at(j)); j++); + episode.number = str.substr(numstart, j - numstart); + // *#-#*v# + if (j < str.length() - 1 && (str.at(j) == 'v' || str.at(j) =='V')) { + numstart = i + 1; + continue; + } else { + return true; + } + + // v# + } else if (str.at(i) == 'v' || str.at(i) == 'V') { + if (episode.number.empty()) { + if (i == str.length() - 1 || !IsNumeric(str.at(i + 1))) return false; + episode.number = str.substr(numstart, i - numstart); + } + episode.version = str.substr(i + 1); + return true; + + // *# of #* + } else if (str.at(i) == separator) { + if (str.length() < i + 5) + return false; + if (str.at(i + 1) == 'o' && + str.at(i + 2) == 'f' && + str.at(i + 3) == separator) { + episode.number = str.substr(numstart, i - numstart); + return true; + } + + // *#x#* + } else if (str.at(i) == 'x') { + if (i == str.length() - 1 || !IsNumeric(str.at(i + 1))) + return false; + for (j = i + 1; j < str.length() && IsNumeric(str.at(j)); j++); + episode.number = str.substr(i + 1, j - i - 1); + if (ToInt(episode.number) < 100) { + return true; + } else { + episode.number.clear(); + return false; + } + + // Japanese counter + } else if (str.at(i) == L'\u8A71') { + episode.number = str.substr(numstart, i - 1); + return true; + + } else { + episode.number.clear(); + return false; + } + } + } + + // [prefix]#* + if (numstart > 0 && episode.number.empty()) { + episode.number = str.substr(numstart, str.length() - numstart); + if (!ValidateEpisodeNumber(episode)) + return false; + return true; + } + + // *# + return false; +} + +bool RecognitionEngine::IsResolution(const std::wstring& str) { + return anime::TranslateResolution(str, true) > 0; +} + +bool RecognitionEngine::IsCountingWord(const std::wstring& str) { + if (str.length() > 2) { + if (EndsWith(str, L"th") || EndsWith(str, L"nd") || EndsWith(str, L"rd") || EndsWith(str, L"st") || + EndsWith(str, L"TH") || EndsWith(str, L"ND") || EndsWith(str, L"RD") || EndsWith(str, L"ST")) { + if (IsNumeric(str.substr(0, str.length() - 2)) || + IsEqual(str, L"FIRST") || + IsEqual(str, L"SECOND") || + IsEqual(str, L"THIRD") || + IsEqual(str, L"FOURTH") || + IsEqual(str, L"FIFTH")) { + return true; + } + } + } + + return false; +} + +bool RecognitionEngine::IsTokenEnclosed(const Token& token) { + return token.encloser == '[' || + token.encloser == '(' || + token.encloser == '{'; +} + +void RecognitionEngine::ReadKeyword(std::vector& output, + const std::wstring& input) { + Split(input, L", ", output); +} + +size_t RecognitionEngine::TokenizeTitle(const std::wstring& str, + const std::wstring& delimiters, + std::vector& tokens) { + size_t index_begin = str.find_first_not_of(delimiters); + + while (index_begin != std::wstring::npos) { + size_t index_end = str.find_first_of(delimiters, index_begin + 1); + tokens.resize(tokens.size() + 1); + if (index_end == std::wstring::npos) { + tokens.back().content = str.substr(index_begin); + break; + } else { + tokens.back().content = str.substr(index_begin, index_end - index_begin); + if (index_begin > 0) + tokens.back().encloser = str.at(index_begin - 1); + index_begin = str.find_first_not_of(delimiters, index_end + 1); + } + } + + return tokens.size(); +} + +bool RecognitionEngine::ValidateEpisodeNumber(anime::Episode& episode) { + int number = ToInt(episode.number); + + if (number <= 0 || number > 1000) { + if (number > 1950 && number < 2050) + episode.year = episode.number; + episode.number.clear(); + return false; + } + + return true; } \ No newline at end of file diff --git a/src/track/recognition.h b/src/track/recognition.h new file mode 100644 index 000000000..49d23501e --- /dev/null +++ b/src/track/recognition.h @@ -0,0 +1,107 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TRACK_RECOGNITION_H +#define TAIGA_TRACK_RECOGNITION_H + +#include +#include +#include +#include + +namespace anime { +class Episode; +class Item; +} +class Token; + +class RecognitionEngine { +public: + RecognitionEngine(); + ~RecognitionEngine() {}; + + void Initialize(); + + anime::Item* MatchDatabase(anime::Episode& episode, + bool in_list = true, + bool reverse = true, + bool strict = true, + bool check_episode = true, + bool check_date = true, + bool give_score = false); + + bool CompareEpisode(anime::Episode& episode, + const anime::Item& anime_item, + bool strict = true, + bool check_episode = true, + bool check_date = true, + bool give_score = false); + + bool ExamineTitle(std::wstring title, + anime::Episode& episode, + bool examine_inside = true, + bool examine_outside = true, + bool examine_number = true, + bool check_extras = true, + bool check_extension = true); + + void ExamineToken(Token& token, anime::Episode& episode, bool compare_extras); + + void CleanTitle(std::wstring& title); + void UpdateCleanTitles(int anime_id); + + std::multimap> GetScores(); + + // Mapped as + std::map scores; + + std::map> clean_titles; + + std::vector audio_keywords; + std::vector video_keywords; + std::vector extra_keywords; + std::vector extra_unsafe_keywords; + std::vector version_keywords; + std::vector valid_extensions; + std::vector episode_keywords; + std::vector episode_prefixes; + +private: + bool CompareTitle(const std::wstring& anime_title, + anime::Episode& episode, + const anime::Item& anime_item, + bool strict = true); + bool ScoreTitle(const anime::Episode& episode, + const anime::Item& anime_item); + + void AppendKeyword(std::wstring& str, const std::wstring& keyword); + bool CompareKeys(const std::wstring& str, const std::vector& keys); + void EraseUnnecessary(std::wstring& str); + void TransliterateSpecial(std::wstring& str); + bool IsEpisodeFormat(const std::wstring& str, anime::Episode& episode, const wchar_t separator = ' '); + bool IsResolution(const std::wstring& str); + bool IsCountingWord(const std::wstring& str); + bool IsTokenEnclosed(const Token& token); + void ReadKeyword(std::vector& output, const std::wstring& input); + size_t TokenizeTitle(const std::wstring& str, const std::wstring& delimiters, std::vector& tokens); + bool ValidateEpisodeNumber(anime::Episode& episode); +}; + +extern RecognitionEngine Meow; + +#endif // TAIGA_TRACK_RECOGNITION_H \ No newline at end of file diff --git a/src/track/search.cpp b/src/track/search.cpp new file mode 100644 index 000000000..e613bfaa2 --- /dev/null +++ b/src/track/search.cpp @@ -0,0 +1,256 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/file.h" +#include "base/foreach.h" +#include "base/log.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" +#include "track/recognition.h" +#include "track/search.h" +#include "ui/ui.h" +#include "win/win_taskbar.h" + +TaigaFileSearchHelper file_search_helper; + +TaigaFileSearchHelper::TaigaFileSearchHelper() + : anime_id_(anime::ID_UNKNOWN), + episode_number_(0) { + // Here we assume that anything less than 10 MiB can't be a valid episode. + minimum_file_size_ = 1024 * 1024 * 10; +} + +bool TaigaFileSearchHelper::OnDirectory(const std::wstring& root, + const std::wstring& name) { + if (!Meow.ExamineTitle(name, episode_, false, false, false, false, false)) + return false; + + foreach_r_(it, AnimeDatabase.items) { + anime::Item& anime_item = it->second; + + if (anime_id_ != anime::ID_UNKNOWN) { + if (anime_item.GetId() != anime_id_) + continue; + } else { + switch (anime_item.GetMyStatus()) { + case anime::kNotInList: + case anime::kCompleted: + case anime::kDropped: + continue; + } + if (!anime_item.GetFolder().empty()) + continue; + } + + if (!Meow.CompareEpisode(episode_, anime_item, true, false, false, false)) + continue; + + anime_item.SetFolder(AddTrailingSlash(root) + name); + + if (anime_id_ != anime::ID_UNKNOWN) { + path_found_ = AddTrailingSlash(root) + name; + if (skip_files_) + return true; + } else { + return false; + } + } + + return false; +} + +bool TaigaFileSearchHelper::OnFile(const std::wstring& root, + const std::wstring& name) { + if (!Meow.ExamineTitle(name, episode_, true, true, true, true, true)) + return false; + + foreach_r_(it, AnimeDatabase.items) { + anime::Item& anime_item = it->second; + + if (anime_id_ != anime::ID_UNKNOWN) { + if (anime_item.GetId() != anime_id_) + continue; + } else { + switch (anime_item.GetMyStatus()) { + case anime::kNotInList: + case anime::kCompleted: + case anime::kDropped: + continue; + } + } + + if (!Meow.CompareEpisode(episode_, anime_item)) + continue; + + int upper_bound = anime::GetEpisodeHigh(episode_.number); + int lower_bound = anime::GetEpisodeLow(episode_.number); + + if (!anime::IsValidEpisode(upper_bound, anime_item.GetEpisodeCount()) || + !anime::IsValidEpisode(lower_bound, anime_item.GetEpisodeCount())) { + LOG(LevelWarning, L"Invalid episode number: " + episode_.number); + LOG(LevelWarning, L"File: " + AddTrailingSlash(root) + name); + continue; + } + + for (int i = lower_bound; i <= upper_bound; i++) + anime_item.SetEpisodeAvailability( + i, true, AddTrailingSlash(root) + name); + + // Check if we've found the episode we were looking for + if (episode_number_ > 0 && + episode_number_ >= lower_bound && + episode_number_ <= upper_bound) { + path_found_ = AddTrailingSlash(root) + name; + return true; + } + // Check if all episodes are available + if (episode_number_ == 0 && + anime_id_ != anime::ID_UNKNOWN && + IsAllEpisodesAvailable(anime_item)) { + return true; + } + + return false; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + +const std::wstring& TaigaFileSearchHelper::path_found() const { + return path_found_; +} + +void TaigaFileSearchHelper::set_anime_id(int anime_id) { + anime_id_ = anime_id; +} + +void TaigaFileSearchHelper::set_episode_number(int episode_number) { + episode_number_ = episode_number; +} + +void TaigaFileSearchHelper::set_path_found(const std::wstring& path_found) { + path_found_ = path_found; +} + +//////////////////////////////////////////////////////////////////////////////// + +void ScanAvailableEpisodes(bool silent) { + ScanAvailableEpisodes(silent, anime::ID_UNKNOWN, 0); +} + +void ScanAvailableEpisodes(bool silent, int anime_id, int episode_number) { + // Check if any root folder is available + if (!silent && Settings.root_folders.empty()) { + ui::OnSettingsRootFoldersEmpty(); + return; + } + + if (!silent) { + TaskbarList.SetProgressState(TBPF_INDETERMINATE); + ui::SetSharedCursor(IDC_WAIT); + ui::ChangeStatusText(L"Scanning available episodes..."); + } + + file_search_helper.set_anime_id(anime_id); + file_search_helper.set_episode_number(episode_number); + file_search_helper.set_path_found(L""); + + auto anime_item = AnimeDatabase.FindItem(anime_id); + bool found = false; + + if (anime_item) { + // Check if the anime folder still exists + if (!anime_item->GetFolder().empty() && + !FolderExists(anime_item->GetFolder())) { + LOG(LevelWarning, L"Folder doesn't exist anymore."); + LOG(LevelWarning, L"Path: " + anime_item->GetFolder()); + anime_item->SetFolder(L""); + for (int i = 1; i <= anime_item->GetAvailableEpisodeCount(); i++) + anime_item->SetEpisodeAvailability(i, false, EmptyString()); + } + + // Search the anime folder for available episodes + if (!anime_item->GetFolder().empty()) { + file_search_helper.set_skip_directories(true); + file_search_helper.set_skip_files(false); + file_search_helper.set_skip_subdirectories(false); + found = file_search_helper.Search(anime_item->GetFolder()); + } + + // Search the cached episode path + if (!found && !anime_item->GetNextEpisodePath().empty()) { + std::wstring next_episode_path = GetPathOnly(anime_item->GetNextEpisodePath()); + if (!IsEqual(next_episode_path, anime_item->GetFolder())) { + file_search_helper.set_skip_directories(true); + file_search_helper.set_skip_files(false); + file_search_helper.set_skip_subdirectories(true); + found = file_search_helper.Search(next_episode_path); + } + } + } + + if (!found) { + // Search root folders for available episodes + foreach_(it, Settings.root_folders) { + bool skip_directories = false; + if (anime_item && !anime_item->GetFolder().empty()) + skip_directories = true; + file_search_helper.set_skip_directories(skip_directories); + file_search_helper.set_skip_files(false); + file_search_helper.set_skip_subdirectories(false); + if (file_search_helper.Search(*it)) { + found = true; + break; + } + } + } + + if (!silent) { + TaskbarList.SetProgressState(TBPF_NOPROGRESS); + ui::SetSharedCursor(IDC_ARROW); + ui::ClearStatusText(); + } +} + +void ScanAvailableEpisodesQuick() { + ScanAvailableEpisodesQuick(anime::ID_UNKNOWN); +} + +void ScanAvailableEpisodesQuick(int anime_id) { + foreach_r_(it, AnimeDatabase.items) { + anime::Item& anime_item = it->second; + + if (anime_id != anime::ID_UNKNOWN && anime_item.GetId() != anime_id) + continue; + if (anime_item.GetFolder().empty()) + continue; + + file_search_helper.set_anime_id(anime_item.GetId()); + file_search_helper.set_episode_number(0); + file_search_helper.set_skip_directories(true); + file_search_helper.set_skip_files(false); + file_search_helper.set_skip_subdirectories(false); + + file_search_helper.Search(anime_item.GetFolder()); + } +} \ No newline at end of file diff --git a/src/track/search.h b/src/track/search.h new file mode 100644 index 000000000..513d90b07 --- /dev/null +++ b/src/track/search.h @@ -0,0 +1,55 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_TRACK_SEARCH_H +#define TAIGA_TRACK_SEARCH_H + +#include + +#include "base/file.h" +#include "library/anime_episode.h" + +class TaigaFileSearchHelper : public FileSearchHelper { +public: + TaigaFileSearchHelper(); + ~TaigaFileSearchHelper() {} + + bool OnDirectory(const std::wstring& root, const std::wstring& name); + bool OnFile(const std::wstring& root, const std::wstring& name); + + const std::wstring& path_found() const; + + void set_anime_id(int anime_id); + void set_episode_number(int episode_number); + void set_path_found(const std::wstring& path_found); + +private: + int anime_id_; + anime::Episode episode_; + int episode_number_; + std::wstring path_found_; +}; + +extern TaigaFileSearchHelper file_search_helper; + +void ScanAvailableEpisodes(bool silent); +void ScanAvailableEpisodes(bool silent, int anime_id, int episode_number); +void ScanAvailableEpisodesQuick(); +void ScanAvailableEpisodesQuick(int anime_id); + +#endif // TAIGA_TRACK_SEARCH_H \ No newline at end of file diff --git a/src/ui/dialog.cpp b/src/ui/dialog.cpp new file mode 100644 index 000000000..fcb6de2d4 --- /dev/null +++ b/src/ui/dialog.cpp @@ -0,0 +1,157 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/process.h" +#include "taiga/resource.h" +#include "ui/dlg/dlg_about.h" +#include "ui/dlg/dlg_anime_info.h" +#include "ui/dlg/dlg_main.h" +#include "ui/dlg/dlg_settings.h" +#include "ui/dlg/dlg_test_recognition.h" +#include "ui/dlg/dlg_torrent.h" +#include "ui/dlg/dlg_update.h" +#include "ui/dialog.h" + +namespace ui { + +class DialogProperties { +public: + DialogProperties(unsigned int resource_id, win::Dialog* dialog, + Dialog parent = kDialogNone, bool modal = false); + + unsigned int resource_id; + win::Dialog* dialog; + Dialog parent; + bool modal; +}; + +DialogProperties::DialogProperties(unsigned int resource_id, + win::Dialog* dialog, + Dialog parent, + bool modal) + : resource_id(resource_id), + dialog(dialog), + parent(parent), + modal(modal) { +} + +std::map dialog_properties; + +void InitializeDialogProperties() { + if (!dialog_properties.empty()) + return; + + dialog_properties.insert(std::make_pair( + kDialogAbout, + DialogProperties(IDD_ABOUT, &DlgAbout, kDialogMain, true))); + dialog_properties.insert(std::make_pair( + kDialogAnimeInformation, + DialogProperties(IDD_ANIME_INFO, &DlgAnime, kDialogMain, false))); + dialog_properties.insert(std::make_pair( + kDialogMain, + DialogProperties(IDD_MAIN, &DlgMain))); + dialog_properties.insert(std::make_pair( + kDialogSettings, + DialogProperties(IDD_SETTINGS, &DlgSettings, kDialogMain, true))); + dialog_properties.insert(std::make_pair( + kDialogTestRecognition, + DialogProperties(IDD_TEST_RECOGNITION, &DlgTestRecognition))); + dialog_properties.insert(std::make_pair( + kDialogUpdate, + DialogProperties(IDD_UPDATE, &DlgUpdate, kDialogMain, true))); +} + +//////////////////////////////////////////////////////////////////////////////// + +void DestroyDialog(Dialog dialog) { + InitializeDialogProperties(); + + auto it = dialog_properties.find(dialog); + + if (it != dialog_properties.end()) + if (it->second.dialog) + it->second.dialog->Destroy(); +} + +void EnableDialogInput(Dialog dialog, bool enable) { + switch (dialog) { + case kDialogMain: + DlgMain.EnableInput(enable); + break; + case kDialogTorrents: + DlgTorrent.EnableInput(enable); + break; + } +} + +HWND GetWindowHandle(Dialog dialog) { + InitializeDialogProperties(); + + auto it = dialog_properties.find(dialog); + + if (it != dialog_properties.end()) + if (it->second.dialog) + return it->second.dialog->GetWindowHandle(); + + return nullptr; +} + +void ShowDialog(Dialog dialog) { + InitializeDialogProperties(); + + auto it = dialog_properties.find(dialog); + + if (it != dialog_properties.end()) { + if (it->second.dialog) { + if (!it->second.dialog->IsWindow()) { + it->second.dialog->Create(it->second.resource_id, + GetWindowHandle(it->second.parent), + it->second.modal); + } else { + ActivateWindow(it->second.dialog->GetWindowHandle()); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void ShowDlgAnimeEdit(int anime_id) { + DlgAnime.SetCurrentId(anime_id); + DlgAnime.SetCurrentPage(kAnimePageMyInfo); + + ShowDialog(kDialogAnimeInformation); +} + +void ShowDlgAnimeInfo(int anime_id) { + DlgAnime.SetCurrentId(anime_id); + DlgAnime.SetCurrentPage(kAnimePageSeriesInfo); + + ShowDialog(kDialogAnimeInformation); +} + +void ShowDlgSettings(int section, int page) { + if (section > 0) + DlgSettings.SetCurrentSection(static_cast(section)); + if (page > 0) + DlgSettings.SetCurrentPage(static_cast(page)); + + ShowDialog(kDialogSettings); +} + +} // namespace ui \ No newline at end of file diff --git a/src/ui/dialog.h b/src/ui/dialog.h new file mode 100644 index 000000000..9c29a2103 --- /dev/null +++ b/src/ui/dialog.h @@ -0,0 +1,48 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DIALOG_H +#define TAIGA_UI_DIALOG_H + +#include + +namespace ui { + +enum Dialog { + kDialogNone, + kDialogAbout, + kDialogAnimeInformation, + kDialogMain, + kDialogSettings, + kDialogTestRecognition, + kDialogTorrents, + kDialogUpdate +}; + +void DestroyDialog(Dialog dialog); +void EnableDialogInput(Dialog dialog, bool enable); +HWND GetWindowHandle(Dialog dialog); +void ShowDialog(Dialog dialog); + +void ShowDlgAnimeEdit(int anime_id); +void ShowDlgAnimeInfo(int anime_id); +void ShowDlgSettings(int section, int page); + +} // namespace ui + +#endif // TAIGA_UI_DIALOG_H \ No newline at end of file diff --git a/src/ui/dlg/dlg_about.cpp b/src/ui/dlg/dlg_about.cpp new file mode 100644 index 000000000..7bbc0ac06 --- /dev/null +++ b/src/ui/dlg/dlg_about.cpp @@ -0,0 +1,183 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include + +#include "base/file.h" +#include "base/gfx.h" +#include "base/string.h" +#include "base/time.h" +#include "taiga/resource.h" +#include "taiga/stats.h" +#include "taiga/taiga.h" +#include "ui/dlg/dlg_about.h" + +namespace ui { + +class AboutDialog DlgAbout; + +const UINT_PTR kTimerTaiga = 74164; + +int AboutDialog::note_list[][2] = { + {84, 1}, // 1/2 + {84, 2}, // 1/4 + {86, 4}, // 1/8 + {84, 2}, // 1/4 + {82, 2}, // 1/4 + {81, 2}, // 1/4 + {77, 4}, // 1/8 + {79, 4}, // 1/8 + {72, 4}, // 1/8 + {77, 1}, // 1/2 + {76, 4}, // 1/8 + {77, 4}, // 1/8 + {79, 4}, // 1/8 + {81, 2}, // 1/4 + {79, 2}, // 1/4 + {77, 2}, // 1/4 + {79, 2}, // 1/4 + {81, 4}, // 1/8 + {84, 1}, // 1/2 + {84, 2}, // 1/2 + {86, 4}, // 1/8 + {84, 2}, // 1/4 + {82, 2}, // 1/4 + {81, 2}, // 1/4 + {77, 4}, // 1/8 + {79, 4}, // 1/8 + {72, 4}, // 1/8 + {77, 1}, // 1/2 + {76, 4}, // 1/8 + {77, 4}, // 1/8 + {76, 4}, // 1/8 + {74, 1} // 1/1 +}; + +float NoteToFrequency(int n) { + if (n < 0 || n > 119) + return -1.0f; + + return 440.0f * pow(2.0f, static_cast(n - 57) / 12.0f); +} + +//////////////////////////////////////////////////////////////////////////////// + +AboutDialog::AboutDialog() + : note_count_(32), note_index_(0) { + RegisterDlgClass(L"TaigaAboutW"); +} + +BOOL AboutDialog::OnDestroy() { + KillTimer(GetWindowHandle(), kTimerTaiga); + + return TRUE; +} + +BOOL AboutDialog::OnInitDialog() { + rich_edit_.Attach(GetDlgItem(IDC_RICHEDIT_ABOUT)); + rich_edit_.SendMessage(EM_AUTOURLDETECT, TRUE /* AURL_ENABLEURL */); + rich_edit_.SetEventMask(ENM_LINK); + + std::wstring text = + L"{\\rtf1\\ansi\\deff0" + L"{\\fonttbl" + L"{\\f0 Segoe UI;}" + L"}" + L"\\deflang1024\\fs18" + L"\\b " TAIGA_APP_NAME L"\\b0\\line " + L"version " + std::wstring(Taiga.version) + L"\\line\\line " + L"\\b Author:\\b0\\line " + L"Eren 'erengy' Okka\\line\\line " + L"\\b Committers and other contributors:\\b0\\line " + L"saka, Diablofan, slevir, LordGravewish, cassist, rr-\\line\\line " + L"\\b Third party stuff that is used by Taiga:\\b0\\line " + L"- Fugue Icons 3.4.5, Copyright (c) 2012, Yusuke Kamiyamane\\line " + L"- JsonCpp 0.6.0, Copyright (c) 2007-2010, Baptiste Lepilleur\\line " + L"- libcurl 7.37.0, Copyright (c) 1996-2014, Daniel Stenberg\\line " + L"- pugixml 1.4, Copyright (c) 2006-2014, Arseny Kapoulkine\\line " + L"- zlib 1.2.8, Copyright (c) 1995-2013, Jean-loup Gailly and Mark Adler\\line\\line " + L"\\b Links:\\b0\\line " + L"- Home page {\\field{\\*\\fldinst HYPERLINK \"http://taiga.erengy.com\"}{\\fldrslt http://taiga.erengy.com}}\\line " + L"- GitHub repository {\\field{\\*\\fldinst HYPERLINK \"https://github.com/erengy/taiga\"}{\\fldrslt https://github.com/erengy/taiga}}\\line " + L"- MyAnimeList club {\\field{\\*\\fldinst HYPERLINK \"http://myanimelist.net/clubs.php?cid=21400\"}{\\fldrslt http://myanimelist.net/clubs.php?cid=21400}}\\line " + L"- Twitter account {\\field{\\*\\fldinst HYPERLINK \"https://twitter.com/taigaapp\"}{\\fldrslt https://twitter.com/taigaapp}}\\line " + L"- IRC channel {\\field{\\*\\fldinst HYPERLINK \"irc://irc.rizon.net/taiga\"}{\\fldrslt irc://irc.rizon.net/taiga}}" + L"}"; + rich_edit_.SetTextEx(WstrToStr(text)); + + return TRUE; +} + +BOOL AboutDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_COMMAND: { + // Icon click + if (HIWORD(wParam) == STN_DBLCLK) { + Stats.tigers_harmed++; + SetTimer(hwnd, kTimerTaiga, 100, nullptr); + note_index_ = 0; + return TRUE; + } + break; + } + + case WM_NOTIFY: { + switch (reinterpret_cast(lParam)->code) { + // Execute link + case EN_LINK: { + auto en_link = reinterpret_cast(lParam); + if (en_link->msg == WM_LBUTTONUP) { + ExecuteLink(rich_edit_.GetTextRange(&en_link->chrg)); + return TRUE; + } + break; + } + } + break; + } + } + + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + +void AboutDialog::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { + win::Dc dc = hdc; + win::Rect rect; + + // Paint background + GetClientRect(&rect); + rect.left = ScaleX(static_cast(48 * 1.5f)); + dc.FillRect(rect, ::GetSysColor(COLOR_WINDOW)); +} + +void AboutDialog::OnTimer(UINT_PTR nIDEvent) { + if (note_index_ == note_count_) { + KillTimer(GetWindowHandle(), kTimerTaiga); + SetText(L"About"); + note_index_ = 0; + } else { + if (note_index_ == 0) + SetText(L"Orange"); + DWORD frequency = static_cast(NoteToFrequency(note_list[note_index_][0])); + DWORD duration = 800 / note_list[note_index_][1]; + Beep(frequency, duration); + note_index_++; + } +} + +} // namespace ui \ No newline at end of file diff --git a/dlg/dlg_about.h b/src/ui/dlg/dlg_about.h similarity index 66% rename from dlg/dlg_about.h rename to src/ui/dlg/dlg_about.h index 99ea5ac39..e6e645e28 100644 --- a/dlg/dlg_about.h +++ b/src/ui/dlg/dlg_about.h @@ -1,45 +1,50 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_ABOUT_H -#define DLG_ABOUT_H - -#include "../std.h" -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" - -// ============================================================================= - -class AboutDialog : public win32::Dialog { -public: - AboutDialog(); - ~AboutDialog() {} - - INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - BOOL OnDestroy(); - BOOL OnInitDialog(); - void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); - void OnTimer(UINT_PTR nIDEvent); - -private: - win32::RichEdit rich_edit_; -}; - -extern class AboutDialog AboutDialog; - -#endif // DLG_ABOUT_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_ABOUT_H +#define TAIGA_UI_DLG_ABOUT_H + +#include "win/ctrl/win_ctrl.h" +#include "win/win_dialog.h" + +namespace ui { + +class AboutDialog : public win::Dialog { +public: + AboutDialog(); + ~AboutDialog() {} + + INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + BOOL OnDestroy(); + BOOL OnInitDialog(); + void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); + void OnTimer(UINT_PTR nIDEvent); + +private: + win::RichEdit rich_edit_; + + size_t note_count_; + int note_index_; + static int note_list[][2]; +}; + +extern AboutDialog DlgAbout; + +} // namespace ui + +#endif // TAIGA_UI_DLG_ABOUT_H \ No newline at end of file diff --git a/dlg/dlg_anime_info.cpp b/src/ui/dlg/dlg_anime_info.cpp similarity index 72% rename from dlg/dlg_anime_info.cpp rename to src/ui/dlg/dlg_anime_info.cpp index eaf75a85b..c31caa0ef 100644 --- a/dlg/dlg_anime_info.cpp +++ b/src/ui/dlg/dlg_anime_info.cpp @@ -1,734 +1,747 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_anime_info.h" -#include "dlg_anime_info_page.h" -#include "dlg_main.h" - -#include "../anime_db.h" -#include "../common.h" -#include "../foreach.h" -#include "../gfx.h" -#include "../history.h" -#include "../myanimelist.h" -#include "../recognition.h" -#include "../resource.h" -#include "../settings.h" -#include "../string.h" -#include "../taiga.h" -#include "../theme.h" - -class AnimeDialog AnimeDialog; -class NowPlayingDialog NowPlayingDialog; - -// ============================================================================= - -AnimeDialog::AnimeDialog() - : anime_id_(anime::ID_UNKNOWN), - current_page_(INFOPAGE_SERIESINFO), - mode_(DIALOG_MODE_ANIME_INFORMATION) { - image_label_.parent = this; -} - -NowPlayingDialog::NowPlayingDialog() { - current_page_ = INFOPAGE_NONE; - mode_ = DIALOG_MODE_NOW_PLAYING; -} - -// ============================================================================= - -BOOL AnimeDialog::OnInitDialog() { - if (mode_ == DIALOG_MODE_NOW_PLAYING) { - SetStyle(DS_CONTROL | WS_CHILD | WS_CLIPCHILDREN, WS_OVERLAPPEDWINDOW); - SetStyle(0, WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE, GWL_EXSTYLE); - SetParent(g_hMain); - } - - // Initialize image label - image_label_.Attach(GetDlgItem(IDC_STATIC_ANIME_IMG)); - - // Initialize title - edit_title_.Attach(GetDlgItem(IDC_EDIT_ANIME_TITLE)); - edit_title_.SendMessage(WM_SETFONT, reinterpret_cast(UI.font_header.Get()), FALSE); - - // Initialize - sys_link_.Attach(GetDlgItem(IDC_LINK_NOWPLAYING)); - sys_link_.Hide(); - - // Initialize tabs - tab_.Attach(GetDlgItem(IDC_TAB_ANIME)); - switch (mode_) { - case DIALOG_MODE_ANIME_INFORMATION: - tab_.InsertItem(0, L"Main information", 0); - if (AnimeDatabase.FindItem(anime_id_)->IsInList()) - tab_.InsertItem(1, L"My list and settings", 0); - break; - case DIALOG_MODE_NOW_PLAYING: - tab_.Hide(); - break; - } - - // Initialize pages - page_series_info.parent = this; - page_my_info.parent = this; - page_series_info.Create(IDD_ANIME_INFO_PAGE01, m_hWindow, false); - switch (mode_) { - case DIALOG_MODE_ANIME_INFORMATION: - page_my_info.Create(IDD_ANIME_INFO_PAGE02, m_hWindow, false); - EnableThemeDialogTexture(page_series_info.GetWindowHandle(), ETDT_ENABLETAB); - EnableThemeDialogTexture(page_my_info.GetWindowHandle(), ETDT_ENABLETAB); - break; - case DIALOG_MODE_NOW_PLAYING: - break; - } - - // Initialize buttons - int show = SW_SHOW; - if (mode_ == DIALOG_MODE_NOW_PLAYING || - !AnimeDatabase.FindItem(anime_id_)->IsInList()) { - show = SW_HIDE; - } - ShowDlgItem(IDOK, show); - ShowDlgItem(IDCANCEL, show); - - // Refresh - SetCurrentPage(current_page_); - Refresh(); - - return TRUE; -} - -void AnimeDialog::OnOK() { - auto anime_item = AnimeDatabase.FindItem(anime_id_); - - if (anime_item->IsInList()) - if (!page_my_info.Save()) - return; - - EndDialog(IDOK); -} - -// ============================================================================= - -BOOL AnimeDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_CTLCOLORSTATIC: { - win32::Dc dc = reinterpret_cast(wParam); - HWND hwnd_control = reinterpret_cast(lParam); - dc.SetBkMode(TRANSPARENT); - if (hwnd_control == GetDlgItem(IDC_EDIT_ANIME_TITLE)) - dc.SetTextColor(theme::COLOR_MAININSTRUCTION); - dc.DetachDC(); - if (hwnd_control == GetDlgItem(IDC_EDIT_ANIME_TITLE)) - return reinterpret_cast(UI.brush_background.Get()); - return reinterpret_cast(::GetSysColorBrush(COLOR_WINDOW)); - } - - case WM_DRAWITEM: { - // Draw anime image - if (wParam == IDC_STATIC_ANIME_IMG) { - LPDRAWITEMSTRUCT dis = reinterpret_cast(lParam); - win32::Rect rect = dis->rcItem; - win32::Dc dc = dis->hDC; - // Paint border - dc.FillRect(rect, ::GetSysColor(COLOR_ACTIVEBORDER)); - rect.Inflate(-1, -1); - dc.FillRect(rect, ::GetSysColor(COLOR_WINDOW)); - rect.Inflate(-1, -1); - // Paint image - auto image = ImageDatabase.GetImage(anime_id_); - if (anime_id_ > anime::ID_UNKNOWN && image) { - dc.SetStretchBltMode(HALFTONE); - dc.StretchBlt(rect.left, rect.top, rect.Width(), rect.Height(), - image->dc.Get(), - 0, 0, image->rect.Width(), image->rect.Height(), - SRCCOPY); - } else { - dc.EditFont(nullptr, 64, TRUE); - dc.SetBkMode(TRANSPARENT); - dc.SetTextColor(::GetSysColor(COLOR_ACTIVEBORDER)); - dc.DrawText(L"?", 1, rect, DT_CENTER | DT_SINGLELINE | DT_VCENTER); - DeleteObject(dc.DetachFont()); - } - dc.DetachDC(); - return TRUE; - } - break; - } - } - - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - -LRESULT AnimeDialog::ImageLabel::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_SETCURSOR: { - if (parent->anime_id_ > anime::ID_UNKNOWN) { - SetSharedCursor(IDC_HAND); - return TRUE; - } - break; - } - } - - return WindowProcDefault(hwnd, uMsg, wParam, lParam); -} - -BOOL AnimeDialog::OnCommand(WPARAM wParam, LPARAM lParam) { - if (LOWORD(wParam) == IDC_STATIC_ANIME_IMG && - HIWORD(wParam) == STN_CLICKED) { - if (anime_id_ > anime::ID_UNKNOWN) { - mal::ViewAnimePage(anime_id_); - return TRUE; - } - } - - return FALSE; -} - -LRESULT AnimeDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { - switch (idCtrl) { - case IDC_LINK_NOWPLAYING: { - switch (pnmh->code) { - // Link click - case NM_CLICK: { - PNMLINK pNMLink = reinterpret_cast(pnmh); - wstring action = pNMLink->item.szUrl; - if (IsEqual(pNMLink->item.szID, L"menu")) { - action = UI.Menus.Show(m_hWindow, 0, 0, pNMLink->item.szUrl); - } else if (IsEqual(pNMLink->item.szID, L"search")) { - action = L"SearchAnime(" + CurrentEpisode.title + L")"; - } else if (IsEqual(pNMLink->item.szUrl, L"score")) { - action = L""; - CurrentEpisode.anime_id = ToInt(pNMLink->item.szID); - auto anime_item = AnimeDatabase.FindItem(CurrentEpisode.anime_id); - if (anime_item) { - anime_item->AddtoUserList(); - auto synonyms = anime_item->GetUserSynonyms(); - synonyms.push_back(CurrentEpisode.title); - anime_item->SetUserSynonyms(synonyms); - Meow.UpdateCleanTitles(anime_item->GetId()); - Settings.Save(); - anime_item->StartWatching(CurrentEpisode); - MainDialog.ChangeStatus(); - } - } - ExecuteAction(action, 0, GetCurrentId()); - return TRUE; - } - - // Custom draw - case NM_CUSTOMDRAW: { - return CDRF_DODEFAULT; - } - } - break; - } - - case IDC_TAB_ANIME: { - switch (pnmh->code) { - // Tab select - case TCN_SELCHANGE: { - SetCurrentPage(tab_.GetCurrentlySelected() + 1); - break; - } - } - break; - } - } - - return 0; -} - -void AnimeDialog::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { - win32::Dc dc = hdc; - win32::Rect rect; - - // Paint background - rect.Copy(lpps->rcPaint); - dc.FillRect(rect, ::GetSysColor(COLOR_WINDOW)); -} - -void AnimeDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { - switch (uMsg) { - case WM_SIZE: { - UpdateControlPositions(&size); - break; - } - } -} - -BOOL AnimeDialog::PreTranslateMessage(MSG* pMsg) { - if (pMsg->message == WM_KEYDOWN) { - // Close window - if (pMsg->wParam == VK_ESCAPE) { - if (mode_ == DIALOG_MODE_ANIME_INFORMATION) { - Destroy(); - return TRUE; - } - } - } - return FALSE; -} - -LRESULT AnimeDialog::Tab::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_PAINT: { - if (::GetUpdateRect(hwnd, NULL, FALSE)) { - PAINTSTRUCT ps; - HDC hdc = ::BeginPaint(hwnd, &ps); - OnPaint(hdc, &ps); - ::EndPaint(hwnd, &ps); - } else { - HDC hdc = ::GetDC(hwnd); - OnPaint(hdc, NULL); - ::ReleaseDC(hwnd, hdc); - } - break; - } - } - - return WindowProcDefault(hwnd, uMsg, wParam, lParam); -} - -void AnimeDialog::Tab::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { - // Adapted from Paul Sanders' example code, located at: - // http://www.glennslayden.com/code/win32/tab-control-background-brush - - ::CallWindowProc(m_PrevWindowProc, m_hWindow, WM_PRINTCLIENT, - reinterpret_cast(hdc), PRF_CLIENT); - - HRGN region = ::CreateRectRgn(0, 0, 0, 0); - RECT rect, lh_corner = {0}, rh_corner = {0}; - - int item_count = GetItemCount(); - int current_item = GetCurrentlySelected(); - int tab_height = 0; - - bool is_vista = win32::GetWinVersion() >= win32::VERSION_VISTA; - bool is_themed_xp = !is_vista && ::IsThemeActive(); - - for (int i = 0; i < item_count; ++i) { - TabCtrl_GetItemRect(m_hWindow, i, &rect); - if (i == current_item) { - tab_height = (rect.bottom - rect.top) + 2; - rect.left -= 1; - rect.right += 1; - rect.top -= 2; - if (i == 0) { - rect.left -= 1; - if (!is_themed_xp) - rect.right += 1; - } - if (i == item_count - 1) - rect.right += 1; - } else { - rect.right -= 1; - if ((is_themed_xp || is_vista) && i == item_count - 1) - rect.right -= 1; - } - - if (is_themed_xp) { - if (i != current_item + 1) { - lh_corner = rect; - lh_corner.bottom = lh_corner.top + 1; - lh_corner.right = lh_corner.left + 1; - } - rh_corner = rect; - rh_corner.bottom = rh_corner.top + 1; - rh_corner.left = rh_corner.right - 1; - } - - HRGN tab_region = ::CreateRectRgn(rect.left, rect.top, rect.right, rect.bottom); - ::CombineRgn(region, region, tab_region, RGN_OR); - ::DeleteObject(tab_region); - - if (lh_corner.right > lh_corner.left) { - HRGN rounded_corner = ::CreateRectRgn( - lh_corner.left, lh_corner.top, lh_corner.right, lh_corner.bottom); - ::CombineRgn(region, region, rounded_corner, RGN_DIFF); - ::DeleteObject(rounded_corner); - } - if (rh_corner.right > rh_corner.left) { - HRGN rounded_corner = ::CreateRectRgn( - rh_corner.left, rh_corner.top, rh_corner.right, rh_corner.bottom); - ::CombineRgn(region, region, rounded_corner, RGN_DIFF); - ::DeleteObject(rounded_corner); - } - } - - GetClientRect(&rect); - HRGN fill_region = ::CreateRectRgn( - rect.left, rect.top, rect.right, rect.top + tab_height); - ::CombineRgn(fill_region, fill_region, region, RGN_DIFF); - ::SelectClipRgn(hdc, fill_region); - HBRUSH hBGBrush = ::GetSysColorBrush(COLOR_WINDOW); - ::FillRgn(hdc, fill_region, hBGBrush); - ::DeleteObject(fill_region); - ::DeleteObject(region); -} - -// ============================================================================= - -bool AnimeDialog::IsTabVisible() const { - return tab_.IsVisible() != FALSE; -} - -int AnimeDialog::GetCurrentId() const { - return anime_id_; -} - -void AnimeDialog::SetCurrentId(int anime_id) { - anime_id_ = anime_id; - - switch (anime_id_) { - case anime::ID_NOTINLIST: - SetCurrentPage(INFOPAGE_NOTRECOGNIZED); - break; - case anime::ID_UNKNOWN: - SetCurrentPage(INFOPAGE_NONE); - break; - default: - SetCurrentPage(INFOPAGE_SERIESINFO); - break; - } - - Refresh(); -} - -void AnimeDialog::SetCurrentPage(int index) { - current_page_ = index; - - if (IsWindow()) { - switch (index) { - case INFOPAGE_NONE: - image_label_.Hide(); - page_my_info.Hide(); - page_series_info.Hide(); - sys_link_.Show(); - break; - case INFOPAGE_SERIESINFO: - image_label_.Show(); - page_my_info.Hide(); - page_series_info.Show(); - sys_link_.Show(mode_ == DIALOG_MODE_NOW_PLAYING); - break; - case INFOPAGE_MYINFO: - image_label_.Show(); - page_series_info.Hide(); - page_my_info.Show(); - sys_link_.Hide(); - break; - case INFOPAGE_NOTRECOGNIZED: - image_label_.Show(); - page_my_info.Hide(); - page_series_info.Hide(); - sys_link_.Show(); - break; - } - tab_.SetCurrentlySelected(index - 1); - } -} - -void AnimeDialog::Refresh(bool image, bool series_info, bool my_info, bool connect) { - if (!IsWindow()) return; - - auto anime_item = AnimeDatabase.FindItem(anime_id_); - - // Load image - if (image) { - ImageDatabase.Load(anime_id_, true, connect); - win32::Rect rect; - GetClientRect(&rect); - SIZE size = {rect.Width(), rect.Height()}; - OnSize(WM_SIZE, 0, size); - RedrawWindow(); - } - - // Set title - if (anime_item) { - if (Settings.Program.List.english_titles) { - SetDlgItemText(IDC_EDIT_ANIME_TITLE, anime_item->GetEnglishTitle(true).c_str()); - } else { - SetDlgItemText(IDC_EDIT_ANIME_TITLE, anime_item->GetTitle().c_str()); - } - } else if (anime_id_ == anime::ID_NOTINLIST) { - SetDlgItemText(IDC_EDIT_ANIME_TITLE, CurrentEpisode.title.c_str()); - } else { - SetDlgItemText(IDC_EDIT_ANIME_TITLE, L"Now Playing"); - } - - // Set content - if (anime_id_ == anime::ID_NOTINLIST) { - wstring content = L"Taiga was unable to recognize this title, and needs your help.\n\n"; - auto scores = Meow.GetScores(); - if (!scores.empty()) { - int count = 0; - content += L"Please choose the correct one from the list below:\n\n"; - foreach_c_(it, scores) { - content += L" \u2022 second) + L"\">" + - AnimeDatabase.items[it->second].GetTitle() + L"" -#ifdef _DEBUG - L" [Score: " + ToWstr(it->first) + L"]" -#endif - L"\n"; - if (++count >= 10) - break; - } - content += L"\nNot in the list? Search MyAnimeList for more."; - } else { - content += L"Search MyAnimeList for this title."; - } - sys_link_.SetText(content); - - } else if (anime_id_ == anime::ID_UNKNOWN) { - wstring content; - Date date_now = GetDate(); - int date_diff = 0; - const int day_limit = 7; - int link_count = 0; - - // Recently watched - vector anime_ids; - foreach_cr_(it, History.queue.items) { - if (it->episode && - std::find(anime_ids.begin(), anime_ids.end(), it->anime_id) == anime_ids.end()) { - auto anime_item = AnimeDatabase.FindItem(it->anime_id); - if (anime_item->GetMyStatus() != mal::MYSTATUS_COMPLETED || - anime_item->GetMyScore() == 0) - anime_ids.push_back(it->anime_id); - } - } - foreach_cr_(it, History.items) { - if (it->episode && - std::find(anime_ids.begin(), anime_ids.end(), it->anime_id) == anime_ids.end()) { - auto anime_item = AnimeDatabase.FindItem(it->anime_id); - if (anime_item->GetMyStatus() != mal::MYSTATUS_COMPLETED || - anime_item->GetMyScore() == 0) - anime_ids.push_back(it->anime_id); - } - } - int recently_watched = 0; - foreach_c_(it, anime_ids) { - auto anime_item = AnimeDatabase.FindItem(*it); - content += L" \u2022 " + anime_item->GetTitle(); - if (anime_item->GetMyStatus() == mal::MYSTATUS_COMPLETED) { - content += L" \u2014 Give a score"; - link_count++; - } else if (anime_item->GetMyStatus() != mal::MYSTATUS_DROPPED) { - int last_watched = anime_item->GetMyLastWatchedEpisode(); - if (last_watched > 0) - content += L" #" + ToWstr(last_watched); - content += L" \u2014 Watch next episode"; - link_count++; - } - content += L"\n"; - recently_watched++; - if (recently_watched >= 20) - break; - } - if (content.empty()) { - content = L"You haven't watched anything recently. " - L"How about trying a random one?\n\n"; - link_count++; - } else { - content = L"Recently watched:\n" + content + L"\n"; - int watched_last_week = 0; - foreach_c_(it, History.queue.items) { - if (!it->episode) continue; - date_diff = date_now - (Date)(it->time.substr(0, 10)); - if (date_diff <= day_limit) - watched_last_week++; - } - foreach_c_(it, History.items) { - if (!it->episode) continue; - date_diff = date_now - (Date)(it->time.substr(0, 10)); - if (date_diff <= day_limit) - watched_last_week++; - } - if (watched_last_week > 0) - content += L"You've watched " + ToWstr(watched_last_week) + L" episodes in the last week.\n\n"; - } - - // Available episodes - int available_episodes = 0; - foreach_c_(it, AnimeDatabase.items) { - if (it->second.IsInList() && it->second.IsNewEpisodeAvailable()) - available_episodes ++; - } - if (available_episodes > 0) - content += L"There are at least " + ToWstr(available_episodes) + L" new episodes available on your computer.\n\n"; - - // Airing times - vector recently_started, recently_finished, upcoming; - foreach_c_(it, AnimeDatabase.items) { - const Date& date_start = it->second.GetDate(anime::DATE_START); - const Date& date_end = it->second.GetDate(anime::DATE_END); - if (date_start.year && date_start.month && date_start.day) { - date_diff = date_now - date_start; - if (date_diff > 0 && date_diff <= day_limit) { - recently_started.push_back(it->first); - continue; - } - date_diff = date_start - date_now; - if (date_diff > 0 && date_diff <= day_limit) { - upcoming.push_back(it->first); - continue; - } - } - if (date_end.year && date_end.month && date_end.day) { - date_diff = date_now - date_end; - if (date_diff > 0 && date_diff <= day_limit) { - recently_finished.push_back(it->first); - continue; - } - } - } - if (!recently_started.empty()) { - content += L"Recently started airing:\n"; - foreach_c_(it, recently_started) - content += L" \u2022 " + AnimeDatabase.FindItem(*it)->GetTitle() + L"\n"; - content += L"\n"; - } - if (!recently_finished.empty()) { - content += L"Recently finished airing:\n"; - foreach_c_(it, recently_finished) - content += L" \u2022 " + AnimeDatabase.FindItem(*it)->GetTitle() + L"\n"; - content += L"\n"; - } - if (!upcoming.empty()) { - content += L"Upcoming:\n"; - foreach_c_(it, upcoming) - content += L" \u2022 " + AnimeDatabase.FindItem(*it)->GetTitle() + L"\n"; - content += L"\n"; - } else { - content += L"View upcoming anime"; - link_count++; - } - - sys_link_.SetText(content); - - /*for (int i = 0; i < link_count; i++) - sys_link_.SetItemState(i, LIS_ENABLED | LIS_HOTTRACK | LIS_DEFAULTCOLORS);*/ - - } else { - int episode_number = GetEpisodeLow(CurrentEpisode.number); - if (episode_number == 0) episode_number = 1; - wstring content = L"Now playing: Episode " + ToWstr(episode_number); - if (!CurrentEpisode.group.empty()) - content += L" by " + CurrentEpisode.group; - content += L"\n"; - if (anime_item->IsInList()) { - content += L"Edit"; - } else { - content += L"Add to list"; - } - content += L" \u2022 Share"; - if (anime_item->GetEpisodeCount() == 0 || - anime_item->GetEpisodeCount() > episode_number) { - content += L" \u2022 Watch next episode"; - } - sys_link_.SetText(content); - } - - // Toggle tabs - if (anime_item && anime_item->IsInList() && - mode_ == DIALOG_MODE_ANIME_INFORMATION) { - tab_.Show(); - } else { - tab_.Hide(); - } - - // Refresh pages - if (series_info) page_series_info.Refresh(anime_id_, connect); - if (my_info) page_my_info.Refresh(anime_id_); - - // Update controls - UpdateControlPositions(); -} - -void AnimeDialog::UpdateControlPositions(const SIZE* size) { - win32::Rect rect; - if (size == nullptr) { - GetClientRect(&rect); - } else { - rect.Set(0, 0, size->cx, size->cy); - } - - rect.Inflate(-ScaleX(WIN_CONTROL_MARGIN) * 2, - -ScaleY(WIN_CONTROL_MARGIN) * 2); - - // Image - if (current_page_ != INFOPAGE_NONE) { - win32::Rect rect_image = rect; - rect_image.right = rect_image.left + ScaleX(150); - auto image = ImageDatabase.GetImage(anime_id_); - if (image) { - rect_image = ResizeRect(rect_image, - image->rect.Width(), image->rect.Height(), - true, true, false); - } else { - rect_image.bottom = rect_image.top + ScaleY(230); - } - image_label_.SetPosition(nullptr, rect_image); - rect.left = rect_image.right + ScaleX(WIN_CONTROL_MARGIN) * 2; - } - - // Title - win32::Rect rect_title; - edit_title_.GetWindowRect(&rect_title); - rect_title.Set(rect.left, rect.top, - rect.right, rect.top + rect_title.Height()); - edit_title_.SetPosition(nullptr, rect_title); - rect.top = rect_title.bottom + ScaleY(WIN_CONTROL_MARGIN); - - // Buttons - if (mode_ == DIALOG_MODE_ANIME_INFORMATION) { - win32::Rect rect_button; - ::GetWindowRect(GetDlgItem(IDOK), &rect_button); - rect.bottom -= rect_button.Height() + ScaleY(WIN_CONTROL_MARGIN) * 2; - } - - // Content - if (mode_ == DIALOG_MODE_NOW_PLAYING) { - if (anime_id_ <= anime::ID_UNKNOWN) { - rect.left += ScaleX(WIN_CONTROL_MARGIN); - sys_link_.SetPosition(nullptr, rect); - } else { - win32::Dc dc = sys_link_.GetDC(); - int text_height = GetTextHeight(dc.Get()); - win32::Rect rect_content = rect; - rect_content.Inflate(-ScaleX(WIN_CONTROL_MARGIN * 2), 0); - rect_content.bottom = rect_content.top + text_height * 2; - sys_link_.SetPosition(nullptr, rect_content); - rect.top = rect_content.bottom + ScaleY(WIN_CONTROL_MARGIN) * 3; - } - } - - // Pages - win32::Rect rect_page = rect; - if (tab_.IsVisible()) { - tab_.SetPosition(nullptr, rect_page); - tab_.AdjustRect(m_hWindow, FALSE, &rect_page); - rect_page.Inflate(-ScaleX(WIN_CONTROL_MARGIN), -ScaleY(WIN_CONTROL_MARGIN)); - } - page_series_info.SetPosition(nullptr, rect_page); - page_my_info.SetPosition(nullptr, rect_page); -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "library/history.h" +#include "library/resource.h" +#include "taiga/resource.h" +#include "taiga/script.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" +#include "track/recognition.h" +#include "ui/dlg/dlg_anime_info.h" +#include "ui/dlg/dlg_anime_info_page.h" +#include "ui/dlg/dlg_main.h" +#include "ui/menu.h" +#include "ui/theme.h" +#include "ui/ui.h" + +namespace ui { + +AnimeDialog DlgAnime; +NowPlayingDialog DlgNowPlaying; + +AnimeDialog::AnimeDialog() + : anime_id_(anime::ID_UNKNOWN), + current_page_(kAnimePageSeriesInfo), + mode_(kDialogModeAnimeInformation) { + image_label_.parent = this; +} + +NowPlayingDialog::NowPlayingDialog() { + current_page_ = kAnimePageNone; + mode_ = kDialogModeNowPlaying; +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL AnimeDialog::OnInitDialog() { + if (mode_ == kDialogModeNowPlaying) { + SetStyle(DS_CONTROL | WS_CHILD | WS_CLIPCHILDREN, WS_OVERLAPPEDWINDOW); + SetStyle(0, WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE, GWL_EXSTYLE); + SetParent(DlgMain.GetWindowHandle()); + } + + // Initialize image label + image_label_.Attach(GetDlgItem(IDC_STATIC_ANIME_IMG)); + + // Initialize title + edit_title_.Attach(GetDlgItem(IDC_EDIT_ANIME_TITLE)); + edit_title_.SendMessage(WM_SETFONT, reinterpret_cast(ui::Theme.GetHeaderFont()), FALSE); + + // Initialize + sys_link_.Attach(GetDlgItem(IDC_LINK_NOWPLAYING)); + sys_link_.Hide(); + + // Initialize tabs + tab_.Attach(GetDlgItem(IDC_TAB_ANIME)); + switch (mode_) { + case kDialogModeAnimeInformation: + tab_.InsertItem(0, L"Main information", 0); + if (AnimeDatabase.FindItem(anime_id_)->IsInList()) + tab_.InsertItem(1, L"My list and settings", 0); + break; + case kDialogModeNowPlaying: + tab_.Hide(); + break; + } + + // Initialize pages + page_series_info.parent = this; + page_my_info.parent = this; + page_series_info.Create(IDD_ANIME_INFO_PAGE01, GetWindowHandle(), false); + switch (mode_) { + case kDialogModeAnimeInformation: + page_my_info.Create(IDD_ANIME_INFO_PAGE02, GetWindowHandle(), false); + EnableThemeDialogTexture(page_series_info.GetWindowHandle(), ETDT_ENABLETAB); + EnableThemeDialogTexture(page_my_info.GetWindowHandle(), ETDT_ENABLETAB); + break; + case kDialogModeNowPlaying: + break; + } + + // Initialize buttons + int show = SW_SHOW; + if (mode_ == kDialogModeNowPlaying || + !AnimeDatabase.FindItem(anime_id_)->IsInList()) { + show = SW_HIDE; + } + ShowDlgItem(IDOK, show); + ShowDlgItem(IDCANCEL, show); + + // Refresh + SetCurrentPage(current_page_); + Refresh(); + + return TRUE; +} + +void AnimeDialog::OnOK() { + auto anime_item = AnimeDatabase.FindItem(anime_id_); + + if (anime_item->IsInList()) + if (!page_my_info.Save()) + return; + + EndDialog(IDOK); +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL AnimeDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_CTLCOLORSTATIC: { + win::Dc dc = reinterpret_cast(wParam); + HWND hwnd_control = reinterpret_cast(lParam); + dc.SetBkMode(TRANSPARENT); + if (hwnd_control == GetDlgItem(IDC_EDIT_ANIME_TITLE)) + dc.SetTextColor(ui::kColorMainInstruction); + dc.DetachDc(); + if (hwnd_control == GetDlgItem(IDC_EDIT_ANIME_TITLE)) + return reinterpret_cast(Theme.GetBackgroundBrush()); + return reinterpret_cast(::GetSysColorBrush(COLOR_WINDOW)); + } + + case WM_DRAWITEM: { + // Draw anime image + if (wParam == IDC_STATIC_ANIME_IMG) { + LPDRAWITEMSTRUCT dis = reinterpret_cast(lParam); + win::Rect rect = dis->rcItem; + win::Dc dc = dis->hDC; + // Paint border + dc.FillRect(rect, ::GetSysColor(COLOR_ACTIVEBORDER)); + rect.Inflate(-1, -1); + dc.FillRect(rect, ::GetSysColor(COLOR_WINDOW)); + rect.Inflate(-1, -1); + // Paint image + auto image = ImageDatabase.GetImage(anime_id_); + if (anime_id_ > anime::ID_UNKNOWN && image) { + dc.SetStretchBltMode(HALFTONE); + dc.StretchBlt(rect.left, rect.top, rect.Width(), rect.Height(), + image->dc.Get(), + 0, 0, image->rect.Width(), image->rect.Height(), + SRCCOPY); + } else { + dc.EditFont(nullptr, 64, TRUE); + dc.SetBkMode(TRANSPARENT); + dc.SetTextColor(::GetSysColor(COLOR_ACTIVEBORDER)); + dc.DrawText(L"?", 1, rect, DT_CENTER | DT_SINGLELINE | DT_VCENTER); + DeleteObject(dc.DetachFont()); + } + dc.DetachDc(); + return TRUE; + } + break; + } + } + + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + +LRESULT AnimeDialog::ImageLabel::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_SETCURSOR: { + if (parent->anime_id_ > anime::ID_UNKNOWN) { + SetSharedCursor(IDC_HAND); + return TRUE; + } + break; + } + } + + return WindowProcDefault(hwnd, uMsg, wParam, lParam); +} + +BOOL AnimeDialog::OnCommand(WPARAM wParam, LPARAM lParam) { + if (LOWORD(wParam) == IDC_STATIC_ANIME_IMG && + HIWORD(wParam) == STN_CLICKED) { + if (anime_id_ > anime::ID_UNKNOWN) { + ExecuteAction(L"ViewAnimePage", 0, anime_id_); + return TRUE; + } + } + + return FALSE; +} + +LRESULT AnimeDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { + switch (idCtrl) { + case IDC_LINK_NOWPLAYING: { + switch (pnmh->code) { + // Link click + case NM_CLICK: { + PNMLINK pNMLink = reinterpret_cast(pnmh); + std::wstring action = pNMLink->item.szUrl; + if (IsEqual(pNMLink->item.szID, L"menu")) { + action = ui::Menus.Show(GetWindowHandle(), 0, 0, pNMLink->item.szUrl); + } else if (IsEqual(pNMLink->item.szID, L"search")) { + action = L"SearchAnime(" + CurrentEpisode.title + L")"; + } else if (IsEqual(pNMLink->item.szUrl, L"score")) { + action = L""; + anime::LinkEpisodeToAnime(CurrentEpisode, ToInt(pNMLink->item.szID)); + } + if (!action.empty()) + ExecuteAction(action, 0, GetCurrentId()); + return TRUE; + } + + // Custom draw + case NM_CUSTOMDRAW: { + return CDRF_DODEFAULT; + } + } + break; + } + + case IDC_TAB_ANIME: { + switch (pnmh->code) { + // Tab select + case TCN_SELCHANGE: { + SetCurrentPage(tab_.GetCurrentlySelected() + 1); + break; + } + } + break; + } + } + + return 0; +} + +void AnimeDialog::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { + win::Dc dc = hdc; + win::Rect rect; + + // Paint background + rect.Copy(lpps->rcPaint); + dc.FillRect(rect, ::GetSysColor(COLOR_WINDOW)); +} + +void AnimeDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { + switch (uMsg) { + case WM_SIZE: { + UpdateControlPositions(&size); + break; + } + } +} + +BOOL AnimeDialog::PreTranslateMessage(MSG* pMsg) { + if (pMsg->message == WM_KEYDOWN) { + switch (pMsg->wParam) { + // Refresh + case VK_F5: + page_series_info.Refresh(anime_id_, true); + page_my_info.Refresh(anime_id_); + return TRUE; + // Close window + case VK_ESCAPE: + if (mode_ == kDialogModeAnimeInformation) { + Destroy(); + return TRUE; + } + break; + } + } + + return FALSE; +} + +LRESULT AnimeDialog::Tab::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_PAINT: { + if (::GetUpdateRect(hwnd, NULL, FALSE)) { + PAINTSTRUCT ps; + HDC hdc = ::BeginPaint(hwnd, &ps); + OnPaint(hdc, &ps); + ::EndPaint(hwnd, &ps); + } else { + HDC hdc = ::GetDC(hwnd); + OnPaint(hdc, NULL); + ::ReleaseDC(hwnd, hdc); + } + break; + } + } + + return WindowProcDefault(hwnd, uMsg, wParam, lParam); +} + +void AnimeDialog::Tab::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { + // Adapted from Paul Sanders' example code, located at: + // http://www.glennslayden.com/code/win32/tab-control-background-brush + + ::CallWindowProc(prev_window_proc_, GetWindowHandle(), WM_PRINTCLIENT, + reinterpret_cast(hdc), PRF_CLIENT); + + HRGN region = ::CreateRectRgn(0, 0, 0, 0); + RECT rect, lh_corner = {0}, rh_corner = {0}; + + int item_count = GetItemCount(); + int current_item = GetCurrentlySelected(); + int tab_height = 0; + + bool is_vista = win::GetVersion() >= win::kVersionVista; + bool is_themed_xp = !is_vista && ::IsThemeActive(); + + for (int i = 0; i < item_count; ++i) { + TabCtrl_GetItemRect(GetWindowHandle(), i, &rect); + if (i == current_item) { + tab_height = (rect.bottom - rect.top) + 2; + rect.left -= 1; + rect.right += 1; + rect.top -= 2; + if (i == 0) { + rect.left -= 1; + if (!is_themed_xp) + rect.right += 1; + } + if (i == item_count - 1) + rect.right += 1; + } else { + rect.right -= 1; + if ((is_themed_xp || is_vista) && i == item_count - 1) + rect.right -= 1; + } + + if (is_themed_xp) { + if (i != current_item + 1) { + lh_corner = rect; + lh_corner.bottom = lh_corner.top + 1; + lh_corner.right = lh_corner.left + 1; + } + rh_corner = rect; + rh_corner.bottom = rh_corner.top + 1; + rh_corner.left = rh_corner.right - 1; + } + + HRGN tab_region = ::CreateRectRgn(rect.left, rect.top, rect.right, rect.bottom); + ::CombineRgn(region, region, tab_region, RGN_OR); + ::DeleteObject(tab_region); + + if (lh_corner.right > lh_corner.left) { + HRGN rounded_corner = ::CreateRectRgn( + lh_corner.left, lh_corner.top, lh_corner.right, lh_corner.bottom); + ::CombineRgn(region, region, rounded_corner, RGN_DIFF); + ::DeleteObject(rounded_corner); + } + if (rh_corner.right > rh_corner.left) { + HRGN rounded_corner = ::CreateRectRgn( + rh_corner.left, rh_corner.top, rh_corner.right, rh_corner.bottom); + ::CombineRgn(region, region, rounded_corner, RGN_DIFF); + ::DeleteObject(rounded_corner); + } + } + + GetClientRect(&rect); + HRGN fill_region = ::CreateRectRgn( + rect.left, rect.top, rect.right, rect.top + tab_height); + ::CombineRgn(fill_region, fill_region, region, RGN_DIFF); + ::SelectClipRgn(hdc, fill_region); + HBRUSH hBGBrush = ::GetSysColorBrush(COLOR_WINDOW); + ::FillRgn(hdc, fill_region, hBGBrush); + ::DeleteObject(fill_region); + ::DeleteObject(region); +} + +//////////////////////////////////////////////////////////////////////////////// + +bool AnimeDialog::IsTabVisible() const { + return tab_.IsVisible() != FALSE; +} + +int AnimeDialog::GetCurrentId() const { + return anime_id_; +} + +void AnimeDialog::SetCurrentId(int anime_id) { + anime_id_ = anime_id; + + switch (anime_id_) { + case anime::ID_NOTINLIST: + SetCurrentPage(kAnimePageNotRecognized); + break; + case anime::ID_UNKNOWN: + SetCurrentPage(kAnimePageNone); + break; + default: + SetCurrentPage(kAnimePageSeriesInfo); + break; + } + + Refresh(); +} + +void AnimeDialog::SetCurrentPage(int index) { + current_page_ = index; + + if (IsWindow()) { + switch (index) { + case kAnimePageNone: + image_label_.Hide(); + page_my_info.Hide(); + page_series_info.Hide(); + sys_link_.Show(); + break; + case kAnimePageSeriesInfo: + image_label_.Show(); + page_my_info.Hide(); + page_series_info.Show(); + sys_link_.Show(mode_ == kDialogModeNowPlaying); + break; + case kAnimePageMyInfo: + image_label_.Show(); + page_series_info.Hide(); + page_my_info.Show(); + sys_link_.Hide(); + break; + case kAnimePageNotRecognized: + image_label_.Show(); + page_my_info.Hide(); + page_series_info.Hide(); + sys_link_.Show(); + break; + } + + tab_.SetCurrentlySelected(index - 1); + } +} + +void AnimeDialog::Refresh(bool image, bool series_info, bool my_info, bool connect) { + if (!IsWindow()) + return; + + auto anime_item = AnimeDatabase.FindItem(anime_id_); + + // Load image + if (image) { + ImageDatabase.Load(anime_id_, true, connect); + win::Rect rect; + GetClientRect(&rect); + SIZE size = {rect.Width(), rect.Height()}; + OnSize(WM_SIZE, 0, size); + RedrawWindow(); + } + + // Set title + if (anime_item) { + if (Settings.GetBool(taiga::kApp_List_DisplayEnglishTitles)) { + SetDlgItemText(IDC_EDIT_ANIME_TITLE, anime_item->GetEnglishTitle(true).c_str()); + } else { + SetDlgItemText(IDC_EDIT_ANIME_TITLE, anime_item->GetTitle().c_str()); + } + } else if (anime_id_ == anime::ID_NOTINLIST) { + SetDlgItemText(IDC_EDIT_ANIME_TITLE, CurrentEpisode.title.c_str()); + } else { + SetDlgItemText(IDC_EDIT_ANIME_TITLE, L"Now Playing"); + } + + // Set content + if (anime_id_ == anime::ID_NOTINLIST) { + std::wstring content = L"Taiga was unable to recognize this title, and it needs your help.\n\n"; + auto scores = Meow.GetScores(); + if (!scores.empty()) { + int count = 0; + content += L"Please choose the correct one from the list below:\n\n"; + foreach_c_(it, scores) { + content += L" \u2022 second) + L"\">" + + AnimeDatabase.items[it->second].GetTitle() + L""; + if (Taiga.debug_mode) + content += L" [Score: " + ToWstr(it->first) + L"]"; + content += L"\n"; + if (++count >= 10) + break; + } + content += L"\nNot in the list? Search for more."; + } else { + content += L"Search for this title."; + } + sys_link_.SetText(content); + + } else if (anime_id_ == anime::ID_UNKNOWN) { + std::wstring content; + Date date_now = GetDate(); + int date_diff = 0; + const int day_limit = 7; + int link_count = 0; + + // Recently watched + std::vector anime_ids; + foreach_cr_(it, History.queue.items) { + if (it->episode && + std::find(anime_ids.begin(), anime_ids.end(), it->anime_id) == anime_ids.end()) { + auto anime_item = AnimeDatabase.FindItem(it->anime_id); + if (anime_item->GetMyStatus() != anime::kCompleted || + anime_item->GetMyScore() == 0) + anime_ids.push_back(it->anime_id); + } + } + foreach_cr_(it, History.items) { + if (it->episode && + std::find(anime_ids.begin(), anime_ids.end(), it->anime_id) == anime_ids.end()) { + auto anime_item = AnimeDatabase.FindItem(it->anime_id); + if (anime_item->GetMyStatus() != anime::kCompleted || + anime_item->GetMyScore() == 0) + anime_ids.push_back(it->anime_id); + } + } + int recently_watched = 0; + foreach_c_(it, anime_ids) { + auto anime_item = AnimeDatabase.FindItem(*it); + content += L" \u2022 " + anime_item->GetTitle(); + if (anime_item->GetMyStatus() == anime::kCompleted) { + content += L" \u2014 Give a score"; + link_count++; + } else if (anime_item->GetMyStatus() != anime::kDropped) { + int last_watched = anime_item->GetMyLastWatchedEpisode(); + if (last_watched > 0) + content += L" #" + ToWstr(last_watched); + content += L" \u2014 Watch next episode"; + link_count++; + } + content += L"\n"; + recently_watched++; + if (recently_watched >= 20) + break; + } + if (content.empty()) { + content = L"You haven't watched anything recently. " + L"How about trying a random one?\n\n"; + link_count++; + } else { + content = L"Recently watched:\n" + content + L"\n"; + int watched_last_week = 0; + foreach_c_(it, History.queue.items) { + if (!it->episode) continue; + date_diff = date_now - (Date)(it->time.substr(0, 10)); + if (date_diff <= day_limit) + watched_last_week++; + } + foreach_c_(it, History.items) { + if (!it->episode) continue; + date_diff = date_now - (Date)(it->time.substr(0, 10)); + if (date_diff <= day_limit) + watched_last_week++; + } + if (watched_last_week > 0) + content += L"You've watched " + ToWstr(watched_last_week) + L" episodes in the last week.\n\n"; + } + + // Available episodes + int available_episodes = 0; + foreach_c_(it, AnimeDatabase.items) { + if (it->second.IsInList() && it->second.IsNewEpisodeAvailable()) + available_episodes ++; + } + if (available_episodes > 0) + content += L"There are at least " + ToWstr(available_episodes) + L" new episodes available on your computer.\n\n"; + + // Airing times + std::vector recently_started, recently_finished, upcoming; + foreach_c_(it, AnimeDatabase.items) { + const Date& date_start = it->second.GetDateStart(); + const Date& date_end = it->second.GetDateEnd(); + if (date_start.year && date_start.month && date_start.day) { + date_diff = date_now - date_start; + if (date_diff > 0 && date_diff <= day_limit) { + recently_started.push_back(it->first); + continue; + } + date_diff = date_start - date_now; + if (date_diff > 0 && date_diff <= day_limit) { + upcoming.push_back(it->first); + continue; + } + } + if (date_end.year && date_end.month && date_end.day) { + date_diff = date_now - date_end; + if (date_diff > 0 && date_diff <= day_limit) { + recently_finished.push_back(it->first); + continue; + } + } + } + if (!recently_started.empty()) { + content += L"Recently started airing:\n"; + foreach_c_(it, recently_started) + content += L" \u2022 " + AnimeDatabase.FindItem(*it)->GetTitle() + L"\n"; + content += L"\n"; + } + if (!recently_finished.empty()) { + content += L"Recently finished airing:\n"; + foreach_c_(it, recently_finished) + content += L" \u2022 " + AnimeDatabase.FindItem(*it)->GetTitle() + L"\n"; + content += L"\n"; + } + if (!upcoming.empty()) { + content += L"Upcoming:\n"; + foreach_c_(it, upcoming) + content += L" \u2022 " + AnimeDatabase.FindItem(*it)->GetTitle() + L"\n"; + content += L"\n"; + } else { + content += L"View upcoming anime"; + link_count++; + } + + sys_link_.SetText(content); + + /* + for (int i = 0; i < link_count; i++) + sys_link_.SetItemState(i, LIS_ENABLED | LIS_HOTTRACK | LIS_DEFAULTCOLORS); + */ + + } else { + int episode_number = anime::GetEpisodeLow(CurrentEpisode.number); + if (episode_number == 0) + episode_number = 1; + std::wstring content = L"Now playing: Episode " + ToWstr(episode_number); + if (!CurrentEpisode.group.empty()) + content += L" by " + CurrentEpisode.group; + content += L"\n"; + if (anime_item->IsInList()) { + content += L"Edit"; + } else { + content += L"Add to list"; + } + content += L" \u2022 Share"; + if (anime_item->GetEpisodeCount() == 0 || + anime_item->GetEpisodeCount() > episode_number) { + content += L" \u2022 Watch next episode"; + } + sys_link_.SetText(content); + } + + // Toggle tabs + if (anime_item && anime_item->IsInList() && + mode_ == kDialogModeAnimeInformation) { + tab_.Show(); + } else { + tab_.Hide(); + } + + // Refresh pages + if (series_info) + page_series_info.Refresh(anime_id_, connect); + if (my_info) + page_my_info.Refresh(anime_id_); + + // Update controls + UpdateControlPositions(); +} + +void AnimeDialog::UpdateControlPositions(const SIZE* size) { + win::Rect rect; + if (size == nullptr) { + GetClientRect(&rect); + } else { + rect.Set(0, 0, size->cx, size->cy); + } + + rect.Inflate(-ScaleX(win::kControlMargin) * 2, + -ScaleY(win::kControlMargin) * 2); + + // Image + if (current_page_ != kAnimePageNone) { + win::Rect rect_image = rect; + rect_image.right = rect_image.left + ScaleX(150); + auto image = ImageDatabase.GetImage(anime_id_); + if (image) { + rect_image = ResizeRect(rect_image, + image->rect.Width(), image->rect.Height(), + true, true, false); + } else { + rect_image.bottom = rect_image.top + ScaleY(230); + } + image_label_.SetPosition(nullptr, rect_image); + rect.left = rect_image.right + ScaleX(win::kControlMargin) * 2; + } + + // Title + win::Rect rect_title; + edit_title_.GetWindowRect(&rect_title); + rect_title.Set(rect.left, rect.top, + rect.right, rect.top + rect_title.Height()); + edit_title_.SetPosition(nullptr, rect_title); + rect.top = rect_title.bottom + ScaleY(win::kControlMargin); + + // Buttons + if (mode_ == kDialogModeAnimeInformation) { + win::Rect rect_button; + ::GetWindowRect(GetDlgItem(IDOK), &rect_button); + rect.bottom -= rect_button.Height() + ScaleY(win::kControlMargin) * 2; + } + + // Content + if (mode_ == kDialogModeNowPlaying) { + if (anime_id_ <= anime::ID_UNKNOWN) { + rect.left += ScaleX(win::kControlMargin); + sys_link_.SetPosition(nullptr, rect); + } else { + win::Dc dc = sys_link_.GetDC(); + int text_height = GetTextHeight(dc.Get()); + win::Rect rect_content = rect; + rect_content.Inflate(-ScaleX(win::kControlMargin * 2), 0); + rect_content.bottom = rect_content.top + text_height * 2; + sys_link_.SetPosition(nullptr, rect_content); + rect.top = rect_content.bottom + ScaleY(win::kControlMargin) * 3; + } + } + + // Pages + win::Rect rect_page = rect; + if (tab_.IsVisible()) { + tab_.SetPosition(nullptr, rect_page); + tab_.AdjustRect(GetWindowHandle(), FALSE, &rect_page); + rect_page.Inflate(-ScaleX(win::kControlMargin), -ScaleY(win::kControlMargin)); + } + page_series_info.SetPosition(nullptr, rect_page); + page_my_info.SetPosition(nullptr, rect_page); +} + +void AnimeDialog::UpdateTitle(bool refreshing) { + if (refreshing) { + SetText(L"Anime Information (Refreshing...)"); + } else { + SetText(L"Anime Information"); + } +} + +} // namespace ui diff --git a/dlg/dlg_anime_info.h b/src/ui/dlg/dlg_anime_info.h similarity index 69% rename from dlg/dlg_anime_info.h rename to src/ui/dlg/dlg_anime_info.h index 6e08e7461..e3046c751 100644 --- a/dlg/dlg_anime_info.h +++ b/src/ui/dlg/dlg_anime_info.h @@ -1,94 +1,95 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_ANIME_INFO_H -#define DLG_ANIME_INFO_H - -#include "../std.h" -#include "../gfx.h" -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" - -#include "dlg_anime_info_page.h" - -enum AnimeDialogMode { - DIALOG_MODE_ANIME_INFORMATION, - DIALOG_MODE_NOW_PLAYING -}; - -// ============================================================================= - -class AnimeDialog : public win32::Dialog { -public: - AnimeDialog(); - virtual ~AnimeDialog() {} - - INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - BOOL OnCommand(WPARAM wParam, LPARAM lParam); - BOOL OnInitDialog(); - LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); - void OnOK(); - void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); - void OnSize(UINT uMsg, UINT nType, SIZE size); - BOOL PreTranslateMessage(MSG* pMsg); - - bool IsTabVisible() const; - int GetCurrentId() const; - void SetCurrentId(int anime_id); - void SetCurrentPage(int index); - void Refresh(bool image = true, - bool series_info = true, - bool my_info = true, - bool connect = true); - void UpdateControlPositions(const SIZE* size = nullptr); - -public: - PageSeriesInfo page_series_info; - PageMyInfo page_my_info; - -protected: - int anime_id_; - int current_page_; - int mode_; - - class ImageLabel : public win32::Window { - public: - LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - AnimeDialog* parent; - } image_label_; - - win32::Edit edit_title_; - win32::SysLink sys_link_; - - class Tab : public win32::Tab { - public: - LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); - } tab_; -}; - -class NowPlayingDialog : public AnimeDialog { -public: - NowPlayingDialog(); - virtual ~NowPlayingDialog() {} -}; - -extern class AnimeDialog AnimeDialog; -extern class NowPlayingDialog NowPlayingDialog; - -#endif // DLG_ANIME_INFO_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_ANIME_INFO_H +#define TAIGA_UI_DLG_ANIME_INFO_H + +#include "base/gfx.h" +#include "ui/dlg/dlg_anime_info_page.h" +#include "win/ctrl/win_ctrl.h" +#include "win/win_dialog.h" + +namespace ui { + +enum AnimeDialogMode { + kDialogModeAnimeInformation, + kDialogModeNowPlaying +}; + +class AnimeDialog : public win::Dialog { +public: + AnimeDialog(); + virtual ~AnimeDialog() {} + + INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + BOOL OnCommand(WPARAM wParam, LPARAM lParam); + BOOL OnInitDialog(); + LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); + void OnOK(); + void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); + void OnSize(UINT uMsg, UINT nType, SIZE size); + BOOL PreTranslateMessage(MSG* pMsg); + + bool IsTabVisible() const; + int GetCurrentId() const; + void SetCurrentId(int anime_id); + void SetCurrentPage(int index); + void Refresh(bool image = true, + bool series_info = true, + bool my_info = true, + bool connect = true); + void UpdateControlPositions(const SIZE* size = nullptr); + void UpdateTitle(bool refreshing = false); + +public: + PageSeriesInfo page_series_info; + PageMyInfo page_my_info; + +protected: + int anime_id_; + int current_page_; + int mode_; + + class ImageLabel : public win::Window { + public: + LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + AnimeDialog* parent; + } image_label_; + + win::Edit edit_title_; + win::SysLink sys_link_; + + class Tab : public win::Tab { + public: + LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); + } tab_; +}; + +class NowPlayingDialog : public AnimeDialog { +public: + NowPlayingDialog(); + ~NowPlayingDialog() {} +}; + +extern AnimeDialog DlgAnime; +extern NowPlayingDialog DlgNowPlaying; + +} // namespace ui + +#endif // TAIGA_UI_DLG_ANIME_INFO_H \ No newline at end of file diff --git a/src/ui/dlg/dlg_anime_info_page.cpp b/src/ui/dlg/dlg_anime_info_page.cpp new file mode 100644 index 000000000..5d0ce252e --- /dev/null +++ b/src/ui/dlg/dlg_anime_info_page.cpp @@ -0,0 +1,456 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "library/history.h" +#include "sync/sync.h" +#include "taiga/resource.h" +#include "taiga/settings.h" +#include "track/recognition.h" +#include "ui/dlg/dlg_anime_info.h" +#include "ui/dlg/dlg_anime_info_page.h" +#include "ui/dlg/dlg_input.h" +#include "ui/theme.h" +#include "win/win_commondialog.h" + +namespace ui { + +PageBaseInfo::PageBaseInfo() + : anime_id_(anime::ID_UNKNOWN), parent(nullptr) { +} + +BOOL PageBaseInfo::OnInitDialog() { + // Set new font for headers + for (int i = 0; i < 3; i++) { + SendDlgItemMessage(IDC_STATIC_HEADER1 + i, WM_SETFONT, + reinterpret_cast(ui::Theme.GetBoldFont()), FALSE); + } + + return TRUE; +} + +INT_PTR PageBaseInfo::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_CTLCOLORSTATIC: { + if (!parent->IsTabVisible()) { + win::Dc dc = reinterpret_cast(wParam); + HWND hwnd_control = reinterpret_cast(lParam); + dc.SetBkMode(TRANSPARENT); + dc.DetachDc(); + if (hwnd_control == GetDlgItem(IDC_EDIT_ANIME_ALT)) + return reinterpret_cast(Theme.GetBackgroundBrush()); + return reinterpret_cast(::GetSysColorBrush(COLOR_WINDOW)); + } + break; + } + } + + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + +void PageBaseInfo::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { + win::Dc dc = hdc; + win::Rect rect; + + // Paint background + rect.Copy(lpps->rcPaint); + if (!parent->IsTabVisible()) + dc.FillRect(rect, ::GetSysColor(COLOR_WINDOW)); + + // Paint header lines + for (int i = 0; i < 3; i++) { + win::Rect rect_header; + win::Window header = GetDlgItem(IDC_STATIC_HEADER1 + i); + header.GetWindowRect(GetWindowHandle(), &rect_header); + rect_header.top = rect_header.bottom + 3; + rect_header.bottom = rect_header.top + 1; + dc.FillRect(rect_header, ::GetSysColor(COLOR_ACTIVEBORDER)); + rect_header.Offset(0, 1); + dc.FillRect(rect_header, ::GetSysColor(COLOR_WINDOW)); + header.SetWindowHandle(nullptr); + } +} + +void PageBaseInfo::OnSize(UINT uMsg, UINT nType, SIZE size) { + switch (uMsg) { + case WM_SIZE: { + win::Rect rect; + rect.Set(0, 0, size.cx, size.cy); + rect.Inflate(-ScaleX(win::kControlMargin), -ScaleY(win::kControlMargin)); + + // Headers + for (int i = 0; i < 3; i++) { + win::Rect rect_header; + win::Window header = GetDlgItem(IDC_STATIC_HEADER1 + i); + header.GetWindowRect(GetWindowHandle(), &rect_header); + rect_header.right = rect.right; + header.SetPosition(nullptr, rect_header); + header.SetWindowHandle(nullptr); + } + + // Redraw + InvalidateRect(); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void PageSeriesInfo::OnSize(UINT uMsg, UINT nType, SIZE size) { + PageBaseInfo::OnSize(uMsg, nType, size); + + switch (uMsg) { + case WM_SIZE: { + win::Rect rect; + rect.Set(0, 0, size.cx, size.cy); + rect.Inflate(-ScaleX(win::kControlMargin), -ScaleY(win::kControlMargin)); + + // Synonyms + win::Rect rect_child; + win::Window window = GetDlgItem(IDC_EDIT_ANIME_ALT); + window.GetWindowRect(GetWindowHandle(), &rect_child); + rect_child.right = rect.right - ScaleX(win::kControlMargin); + window.SetPosition(nullptr, rect_child); + + // Details + window.SetWindowHandle(GetDlgItem(IDC_STATIC_ANIME_DETAILS)); + window.GetWindowRect(GetWindowHandle(), &rect_child); + rect_child.right = rect.right - ScaleX(win::kControlMargin); + window.SetPosition(nullptr, rect_child); + + // Synopsis + window.SetWindowHandle(GetDlgItem(IDC_EDIT_ANIME_SYNOPSIS)); + window.GetWindowRect(GetWindowHandle(), &rect_child); + rect_child.right = rect.right - ScaleX(win::kControlMargin); + rect_child.bottom = rect.bottom; + window.SetPosition(nullptr, rect_child); + window.SetWindowHandle(nullptr); + } + } +} + +void PageSeriesInfo::Refresh(int anime_id, bool connect) { + if (anime_id <= anime::ID_UNKNOWN) + return; + + anime_id_ = anime_id; + auto anime_item = AnimeDatabase.FindItem(anime_id_); + + // Update window title + parent->UpdateTitle(false); + + // Set synonyms + std::wstring text = Join(anime_item->GetSynonyms(), L", "); + if (text.empty()) + text = L"-"; + SetDlgItemText(IDC_EDIT_ANIME_ALT, text.c_str()); + + // Set information + #define ADD_INFOLINE(x, y) (x.empty() ? y : x) + text = anime::TranslateType(anime_item->GetType()) + L"\n" + + anime::TranslateNumber(anime_item->GetEpisodeCount(), L"Unknown") + L"\n" + + anime::TranslateStatus(anime_item->GetAiringStatus()) + L"\n" + + anime::TranslateDateToSeasonString(anime_item->GetDateStart()) + L"\n" + + (anime_item->GetGenres().empty() ? L"Unknown" : Join(anime_item->GetGenres(), L", ")) + L"\n" + + (anime_item->GetProducers().empty() ? L"Unknown" : Join(anime_item->GetProducers(), L", ")) + L"\n" + + ADD_INFOLINE(anime_item->GetScore(), L"0.00"); + #undef ADD_INFOLINE + SetDlgItemText(IDC_STATIC_ANIME_DETAILS, text.c_str()); + + // Set synopsis + text = anime_item->GetSynopsis(); + SetDlgItemText(IDC_EDIT_ANIME_SYNOPSIS, text.c_str()); + + // Get new data if necessary + if (connect && anime::MetadataNeedsRefresh(*anime_item)) { + parent->UpdateTitle(true); + sync::GetMetadataById(anime_id_); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL PageMyInfo::OnCommand(WPARAM wParam, LPARAM lParam) { + auto anime_item = AnimeDatabase.FindItem(anime_id_); + + switch (LOWORD(wParam)) { + // Browse anime folder + case IDC_BUTTON_BROWSE: { + std::wstring default_path, path; + if (!anime_item->GetFolder().empty()) { + default_path = anime_item->GetFolder(); + } else if (!Settings.root_folders.empty()) { + default_path = Settings.root_folders.front(); + } + if (win::BrowseForFolder(GetWindowHandle(), L"Choose an anime folder", default_path, path)) { + SetDlgItemText(IDC_EDIT_ANIME_FOLDER, path.c_str()); + } + return TRUE; + } + + // User changed rewatching checkbox + case IDC_CHECK_ANIME_REWATCH: + if (HIWORD(wParam) == BN_CLICKED) { + win::ComboBox combobox = GetDlgItem(IDC_COMBO_ANIME_STATUS); + win::Spin spin = GetDlgItem(IDC_SPIN_PROGRESS); + int episode_value = 0; + spin.GetPos32(episode_value); + if (IsDlgButtonChecked(IDC_CHECK_ANIME_REWATCH)) { + if (anime_item->GetMyStatus() == anime::kCompleted && + episode_value == anime_item->GetEpisodeCount()) + spin.SetPos32(0); + combobox.Enable(FALSE); + combobox.SetCurSel(anime::kCompleted - 1); + } else { + if (episode_value == 0) + spin.SetPos32(anime_item->GetMyLastWatchedEpisode()); + combobox.Enable(); + combobox.SetCurSel(anime_item->GetMyStatus() - 1); + } + spin.SetWindowHandle(nullptr); + combobox.SetWindowHandle(nullptr); + return TRUE; + } + break; + + // User changed status dropdown + case IDC_COMBO_ANIME_STATUS: + if (HIWORD(wParam) == CBN_SELENDOK) { + // Selected "Completed" + win::ComboBox combobox = GetDlgItem(IDC_COMBO_ANIME_STATUS); + if (combobox.GetItemData(combobox.GetCurSel()) == anime::kCompleted) + if (anime_item->GetMyStatus() != anime::kCompleted && + anime_item->GetEpisodeCount() > 0) + SendDlgItemMessage(IDC_SPIN_PROGRESS, UDM_SETPOS32, 0, anime_item->GetEpisodeCount()); + combobox.SetWindowHandle(nullptr); + return TRUE; + } + break; + } + + return FALSE; +} + +LRESULT PageMyInfo::OnNotify(int idCtrl, LPNMHDR pnmh) { + switch (pnmh->idFrom) { + case IDC_LINK_ANIME_FANSUB: + switch (pnmh->code) { + case NM_CLICK: { + // Set/change fansub group preference + std::vector groups; + anime::GetFansubFilter(anime_id_, groups); + std::wstring text = Join(groups, L", "); + InputDialog dlg; + dlg.title = AnimeDatabase.FindItem(anime_id_)->GetTitle(); + dlg.info = L"Please enter your fansub group preference for this title:"; + dlg.text = text; + dlg.Show(parent->GetWindowHandle()); + if (dlg.result == IDOK) + if (anime::SetFansubFilter(anime_id_, dlg.text)) + RefreshFansubPreference(); + return TRUE; + } + } + } + + return 0; +} + +void PageMyInfo::Refresh(int anime_id) { + if (anime_id <= anime::ID_UNKNOWN) + return; + + anime_id_ = anime_id; + auto anime_item = AnimeDatabase.FindItem(anime_id_); + + if (!anime_item->IsInList()) + return; + + // Episodes watched + SendDlgItemMessage(IDC_SPIN_PROGRESS, UDM_SETRANGE32, 0, + anime_item->GetEpisodeCount() > 0 ? anime_item->GetEpisodeCount() : 9999); + SendDlgItemMessage(IDC_SPIN_PROGRESS, UDM_SETPOS32, 0, anime_item->GetMyLastWatchedEpisode()); + + // Re-watching + CheckDlgButton(IDC_CHECK_ANIME_REWATCH, anime_item->GetMyRewatching()); + EnableDlgItem(IDC_CHECK_ANIME_REWATCH, anime_item->GetMyStatus() == anime::kCompleted); + + // Status + win::ComboBox combobox = GetDlgItem(IDC_COMBO_ANIME_STATUS); + if (combobox.GetCount() == 0) + for (int i = anime::kMyStatusFirst; i < anime::kMyStatusLast; i++) + combobox.AddItem(anime::TranslateMyStatus(i, false).c_str(), i); + combobox.SetCurSel(anime_item->GetMyStatus() - 1); + combobox.Enable(!anime_item->GetMyRewatching()); + combobox.SetWindowHandle(nullptr); + + // Score + combobox.SetWindowHandle(GetDlgItem(IDC_COMBO_ANIME_SCORE)); + if (combobox.GetCount() == 0) { + combobox.AddString(L"(10) Masterpiece"); + combobox.AddString(L"(9) Great"); + combobox.AddString(L"(8) Very Good"); + combobox.AddString(L"(7) Good"); + combobox.AddString(L"(6) Fine"); + combobox.AddString(L"(5) Average"); + combobox.AddString(L"(4) Bad"); + combobox.AddString(L"(3) Very Bad"); + combobox.AddString(L"(2) Horrible"); + combobox.AddString(L"(1) Unwatchable"); + combobox.AddString(L"(0) No Score"); + } + combobox.SetCurSel(10 - anime_item->GetMyScore()); + combobox.SetWindowHandle(nullptr); + + // Tags + win::Edit edit = GetDlgItem(IDC_EDIT_ANIME_TAGS); + edit.SetCueBannerText(L"Enter tags here, separated by a comma (e.g. tag1, tag2)"); + edit.SetText(anime_item->GetMyTags()); + edit.SetWindowHandle(nullptr); + + // Date limits and defaults + if (anime::IsValidDate(anime_item->GetDateStart())) { + SYSTEMTIME stSeriesStart = anime_item->GetDateStart(); + SendDlgItemMessage(IDC_DATETIME_START, DTM_SETRANGE, GDTR_MIN, (LPARAM)&stSeriesStart); + SendDlgItemMessage(IDC_DATETIME_START, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM)&stSeriesStart); + SendDlgItemMessage(IDC_DATETIME_FINISH, DTM_SETRANGE, GDTR_MIN, (LPARAM)&stSeriesStart); + SendDlgItemMessage(IDC_DATETIME_FINISH, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM)&stSeriesStart); + } + if (anime::IsValidDate(anime_item->GetDateEnd())) { + SYSTEMTIME stSeriesEnd = anime_item->GetDateEnd(); + SendDlgItemMessage(IDC_DATETIME_FINISH, DTM_SETRANGE, GDTR_MIN, (LPARAM)&stSeriesEnd); + SendDlgItemMessage(IDC_DATETIME_FINISH, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM)&stSeriesEnd); + } + // Start date + if (anime::IsValidDate(anime_item->GetMyDateStart())) { + SYSTEMTIME stMyStart = anime_item->GetMyDateStart(); + SendDlgItemMessage(IDC_DATETIME_START, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM)&stMyStart); + } else { + SendDlgItemMessage(IDC_DATETIME_START, DTM_SETSYSTEMTIME, GDT_NONE, 0); + } + // Finish date + if (anime::IsValidDate(anime_item->GetMyDateEnd())) { + SYSTEMTIME stMyFinish = anime_item->GetMyDateEnd(); + SendDlgItemMessage(IDC_DATETIME_FINISH, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM)&stMyFinish); + } else { + SendDlgItemMessage(IDC_DATETIME_FINISH, DTM_SETSYSTEMTIME, GDT_NONE, 0); + } + + // Alternative titles + edit.SetWindowHandle(GetDlgItem(IDC_EDIT_ANIME_ALT)); + edit.SetCueBannerText(L"Enter alternative titles here, separated by a semicolon (e.g. Title 1; Title 2)"); + edit.SetText(Join(anime_item->GetUserSynonyms(), L"; ")); + edit.SetWindowHandle(nullptr); + CheckDlgButton(IDC_CHECK_ANIME_ALT, anime_item->GetUseAlternative()); + + // Folder + edit.SetWindowHandle(GetDlgItem(IDC_EDIT_ANIME_FOLDER)); + edit.SetText(anime_item->GetFolder()); + edit.SetWindowHandle(nullptr); + + // Fansub group + RefreshFansubPreference(); +} + +void PageMyInfo::RefreshFansubPreference() { + if (anime_id_ <= anime::ID_UNKNOWN) + return; + + std::wstring text; + std::vector groups; + + if (anime::GetFansubFilter(anime_id_, groups)) { + foreach_(it, groups) { + if (!text.empty()) + text += L" or "; + text += L"\"" + *it + L"\""; + } + } else { + text = L"None"; + } + + text = L"Fansub group preference: " + text + L" (Change)"; + SetDlgItemText(IDC_LINK_ANIME_FANSUB, text.c_str()); +} + +bool PageMyInfo::Save() { + auto anime_item = AnimeDatabase.FindItem(anime_id_); + + // Create item + HistoryItem history_item; + history_item.anime_id = anime_id_; + history_item.mode = taiga::kHttpServiceUpdateLibraryEntry; + + // Episodes watched + history_item.episode = GetDlgItemInt(IDC_EDIT_ANIME_PROGRESS); + if (!anime::IsValidEpisode(*history_item.episode, -1, anime_item->GetEpisodeCount())) { + std::wstring msg = L"Please enter a valid episode number between 0-" + + ToWstr(anime_item->GetEpisodeCount()) + L"."; + MessageBox(msg.c_str(), L"Episodes watched", MB_OK | MB_ICONERROR); + return false; + } + + // Re-watching + history_item.enable_rewatching = IsDlgButtonChecked(IDC_CHECK_ANIME_REWATCH); + + // Score + history_item.score = 10 - GetComboSelection(IDC_COMBO_ANIME_SCORE); + + // Status + history_item.status = GetComboSelection(IDC_COMBO_ANIME_STATUS) + 1; + + // Tags + history_item.tags = GetDlgItemText(IDC_EDIT_ANIME_TAGS); + + // Start date + SYSTEMTIME stMyStart; + if (SendDlgItemMessage(IDC_DATETIME_START, DTM_GETSYSTEMTIME, 0, + reinterpret_cast(&stMyStart)) == GDT_NONE) { + history_item.date_start = Date(); + } else { + history_item.date_start = Date(stMyStart.wYear, stMyStart.wMonth, stMyStart.wDay); + } + // Finish date + SYSTEMTIME stMyFinish; + if (SendDlgItemMessage(IDC_DATETIME_FINISH, DTM_GETSYSTEMTIME, 0, + reinterpret_cast(&stMyFinish)) == GDT_NONE) { + history_item.date_finish = Date(); + } else { + history_item.date_finish = Date(stMyFinish.wYear, stMyFinish.wMonth, stMyFinish.wDay); + } + + // Alternative titles + anime_item->SetUserSynonyms(GetDlgItemText(IDC_EDIT_ANIME_ALT)); + anime_item->SetUseAlternative(IsDlgButtonChecked(IDC_CHECK_ANIME_ALT) == TRUE); + Meow.UpdateCleanTitles(anime_id_); + + // Folder + anime_item->SetFolder(GetDlgItemText(IDC_EDIT_ANIME_FOLDER)); + + // Save settings + Settings.Save(); + + // Add item to queue + History.queue.Add(history_item); + return true; +} + +} // namespace ui \ No newline at end of file diff --git a/dlg/dlg_anime_info_page.h b/src/ui/dlg/dlg_anime_info_page.h similarity index 68% rename from dlg/dlg_anime_info_page.h rename to src/ui/dlg/dlg_anime_info_page.h index c5b7e7a0b..6e32c994b 100644 --- a/dlg/dlg_anime_info_page.h +++ b/src/ui/dlg/dlg_anime_info_page.h @@ -1,69 +1,70 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_ANIME_INFO_PAGE_H -#define DLG_ANIME_INFO_PAGE_H - -#include "../std.h" -#include "../win32/win_dialog.h" - -// ============================================================================= - -enum AnimeInfoPageType { - INFOPAGE_NONE, - INFOPAGE_SERIESINFO, - INFOPAGE_MYINFO, - INFOPAGE_NOTRECOGNIZED -}; - -class AnimeDialog; - -class PageBaseInfo : public win32::Dialog { - public: - PageBaseInfo(); - virtual ~PageBaseInfo() {} - - INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - BOOL OnInitDialog(); - void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); - void OnSize(UINT uMsg, UINT nType, SIZE size); - - AnimeDialog* parent; - - protected: - int anime_id_; -}; - -class PageSeriesInfo : public PageBaseInfo { - public: - void OnSize(UINT uMsg, UINT nType, SIZE size); - - void Refresh(int anime_id, bool connect); -}; - -class PageMyInfo : public PageBaseInfo { - public: - BOOL OnCommand(WPARAM wParam, LPARAM lParam); - LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); - bool Save(); - - void Refresh(int anime_id); - void RefreshFansubPreference(); -}; - -#endif // DLG_ANIME_INFO_PAGE_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_ANIME_INFO_PAGE_H +#define TAIGA_UI_DLG_ANIME_INFO_PAGE_H + +#include "win/win_dialog.h" + +namespace ui { + +enum AnimePageType { + kAnimePageNone, + kAnimePageSeriesInfo, + kAnimePageMyInfo, + kAnimePageNotRecognized +}; + +class AnimeDialog; + +class PageBaseInfo : public win::Dialog { +public: + PageBaseInfo(); + virtual ~PageBaseInfo() {} + + INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + BOOL OnInitDialog(); + void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); + void OnSize(UINT uMsg, UINT nType, SIZE size); + + ui::AnimeDialog* parent; + +protected: + int anime_id_; +}; + +class PageSeriesInfo : public PageBaseInfo { +public: + void OnSize(UINT uMsg, UINT nType, SIZE size); + + void Refresh(int anime_id, bool connect); +}; + +class PageMyInfo : public PageBaseInfo { +public: + BOOL OnCommand(WPARAM wParam, LPARAM lParam); + LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); + bool Save(); + + void Refresh(int anime_id); + void RefreshFansubPreference(); +}; + +} // namespace ui + +#endif // TAIGA_UI_DLG_ANIME_INFO_PAGE_H \ No newline at end of file diff --git a/dlg/dlg_anime_list.cpp b/src/ui/dlg/dlg_anime_list.cpp similarity index 67% rename from dlg/dlg_anime_list.cpp rename to src/ui/dlg/dlg_anime_list.cpp index 48d7c440e..3a95a8671 100644 --- a/dlg/dlg_anime_list.cpp +++ b/src/ui/dlg/dlg_anime_list.cpp @@ -1,1121 +1,1183 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_anime_list.h" - -#include "dlg_main.h" -#include "dlg_torrent.h" - -#include "../anime_db.h" -#include "../anime_filter.h" -#include "../common.h" -#include "../gfx.h" -#include "../myanimelist.h" -#include "../resource.h" -#include "../settings.h" -#include "../string.h" -#include "../taiga.h" -#include "../theme.h" - -#include "../win32/win_gdi.h" - -class AnimeListDialog AnimeListDialog; - -// ============================================================================= - -AnimeListDialog::AnimeListDialog() - : current_id_(anime::ID_UNKNOWN), - current_status_(mal::MYSTATUS_WATCHING) { -} - -BOOL AnimeListDialog::OnInitDialog() { - // Create tab control - tab.Attach(GetDlgItem(IDC_TAB_MAIN)); - - // Create main list - listview.parent = this; - listview.Attach(GetDlgItem(IDC_LIST_MAIN)); - listview.SetExtendedStyle(LVS_EX_AUTOSIZECOLUMNS | - LVS_EX_DOUBLEBUFFER | - LVS_EX_FULLROWSELECT | - LVS_EX_INFOTIP | - LVS_EX_LABELTIP | - LVS_EX_TRACKSELECT); - listview.SetBkImage(UI.list_background.bitmap, UI.list_background.flags, - UI.list_background.offset_x, UI.list_background.offset_y); - listview.SetHoverTime(60 * 1000); - listview.SetImageList(UI.ImgList16.GetHandle()); - listview.Sort(Settings.Program.List.sort_column, Settings.Program.List.sort_order, LIST_SORTTYPE_DEFAULT, ListViewCompareProc); - listview.SetTheme(); - - // Create list tooltips - listview.tooltips.Create(listview.GetWindowHandle()); - listview.tooltips.SetDelayTime(30000, -1, 0); - - // Insert list columns - listview.InsertColumn(0, GetSystemMetrics(SM_CXSCREEN), 340, LVCFMT_LEFT, L"Anime title"); - listview.InsertColumn(1, 200, 200, LVCFMT_CENTER, L"Progress"); - listview.InsertColumn(2, 62, 62, LVCFMT_CENTER, L"Score"); - listview.InsertColumn(3, 62, 62, LVCFMT_CENTER, L"Type"); - listview.InsertColumn(4, 105, 105, LVCFMT_RIGHT, L"Season"); - - // Insert tabs and list groups - listview.InsertGroup(mal::MYSTATUS_NOTINLIST, mal::TranslateMyStatus(mal::MYSTATUS_NOTINLIST, false).c_str()); - for (int i = mal::MYSTATUS_WATCHING; i <= mal::MYSTATUS_PLANTOWATCH; i++) { - if (i != mal::MYSTATUS_UNKNOWN) { - tab.InsertItem(i - 1, mal::TranslateMyStatus(i, true).c_str(), (LPARAM)i); - listview.InsertGroup(i, mal::TranslateMyStatus(i, false).c_str()); - } - } - - // Track mouse leave event for the list view - TRACKMOUSEEVENT tme = {0}; - tme.cbSize = sizeof(TRACKMOUSEEVENT); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = listview.GetWindowHandle(); - TrackMouseEvent(&tme); - - // Refresh - RefreshList(mal::MYSTATUS_WATCHING); - RefreshTabs(mal::MYSTATUS_WATCHING); - - // Success - return TRUE; -} - -// ============================================================================= - -INT_PTR AnimeListDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_MOUSEMOVE: { - // Drag list item - if (listview.dragging) { - bool allow_drop = false; - - if (tab.HitTest() > -1) - allow_drop = true; - - if (!allow_drop) { - POINT pt; - GetCursorPos(&pt); - win32::Rect rect_edit; - MainDialog.edit.GetWindowRect(&rect_edit); - if (rect_edit.PtIn(pt)) - allow_drop = true; - } - - if (!allow_drop) { - TVHITTESTINFO ht = {0}; - MainDialog.treeview.HitTest(&ht, true); - if (ht.flags & TVHT_ONITEM) { - int index = MainDialog.treeview.GetItemData(ht.hItem); - switch (index) { - case SIDEBAR_ITEM_SEARCH: - case SIDEBAR_ITEM_FEEDS: - allow_drop = true; - break; - } - } - } - - POINT pt; - GetCursorPos(&pt); - ::ScreenToClient(MainDialog.GetWindowHandle(), &pt); - listview.drag_image.DragMove(pt.x + 16, pt.y + 32); - SetSharedCursor(allow_drop ? IDC_ARROW : IDC_NO); - } - break; - } - - case WM_LBUTTONUP: { - // Drop list item - if (listview.dragging) { - listview.drag_image.DragLeave(MainDialog.GetWindowHandle()); - listview.drag_image.EndDrag(); - listview.drag_image.Destroy(); - listview.dragging = false; - ReleaseCapture(); - - int anime_id = GetCurrentId(); - auto anime_item = GetCurrentItem(); - if (!anime_item) - break; - - int tab_index = tab.HitTest(); - if (tab_index > -1) { - int status = tab.GetItemParam(tab_index); - if (anime_item->IsInList()) { - ExecuteAction(L"EditStatus(" + ToWstr(status) + L")", 0, anime_id); - } else { - ExecuteAction(L"AddToListAs(" + ToWstr(status) + L")", 0, anime_id); - } - break; - } - - wstring text = Settings.Program.List.english_titles ? - anime_item->GetEnglishTitle(true) : anime_item->GetTitle(); - - POINT pt; - GetCursorPos(&pt); - win32::Rect rect_edit; - MainDialog.edit.GetWindowRect(&rect_edit); - if (rect_edit.PtIn(pt)) { - MainDialog.edit.SetText(text); - break; - } - - TVHITTESTINFO ht = {0}; - MainDialog.treeview.HitTest(&ht, true); - if (ht.flags & TVHT_ONITEM) { - int index = MainDialog.treeview.GetItemData(ht.hItem); - switch (index) { - case SIDEBAR_ITEM_SEARCH: - ExecuteAction(L"SearchAnime(" + text + L")"); - break; - case SIDEBAR_ITEM_FEEDS: - TorrentDialog.Search(Settings.RSS.Torrent.search_url, anime_id); - break; - } - } - } - break; - } - - case WM_MEASUREITEM: { - if (wParam == IDC_LIST_MAIN) { - auto mis = reinterpret_cast(lParam); - mis->itemHeight = 48; - return TRUE; - } - break; - } - - case WM_DRAWITEM: { - if (wParam == IDC_LIST_MAIN) { - auto dis = reinterpret_cast(lParam); - win32::Dc dc = dis->hDC; - win32::Rect rect = dis->rcItem; - - int anime_id = dis->itemData; - auto anime_item = AnimeDatabase.FindItem(anime_id); - if (!anime_item) return TRUE; - - if ((dis->itemState & ODS_SELECTED) == ODS_SELECTED) { - dc.FillRect(rect, theme::COLOR_LIGHTBLUE); - } - rect.Inflate(-2, -2); - dc.FillRect(rect, theme::COLOR_LIGHTGRAY); - - // Draw image - win32::Rect rect_image = rect; - rect_image.right = rect_image.left + static_cast(rect_image.Height() / 1.4); - dc.FillRect(rect_image, theme::COLOR_GRAY); - if (ImageDatabase.Load(anime_id, false, false)) { - auto image = ImageDatabase.GetImage(anime_id); - int sbm = dc.SetStretchBltMode(HALFTONE); - dc.StretchBlt(rect_image.left, rect_image.top, - rect_image.Width(), rect_image.Height(), - image->dc.Get(), 0, 0, - image->rect.Width(), - image->rect.Height(), - SRCCOPY); - dc.SetStretchBltMode(sbm); - } - - // Draw title - rect.left += rect_image.Width() + 8; - int bk_mode = dc.SetBkMode(TRANSPARENT); - dc.AttachFont(UI.font_header); - dc.DrawText(anime_item->GetTitle().c_str(), anime_item->GetTitle().length(), rect, - DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE); - dc.DetachFont(); - - // Draw second line of information - rect.top += 20; - COLORREF text_color = dc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); - wstring text = ToWstr(anime_item->GetMyLastWatchedEpisode()) + L"/" + - ToWstr(anime_item->GetEpisodeCount()); - dc.DrawText(text.c_str(), -1, rect, - DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE); - dc.SetTextColor(text_color); - dc.SetBkMode(bk_mode); - - // Draw progress bar - rect.left -= 2; - rect.top += 12; - rect.bottom = rect.top + 12; - rect.right -= 8; - listview.DrawProgressBar(dc.Get(), &rect, dis->itemID, 0, *anime_item); - - dc.DetachDC(); - return TRUE; - } - break; - } - - // Forward mouse wheel messages to the list - case WM_MOUSEWHEEL: { - return listview.SendMessage(uMsg, wParam, lParam); - } - } - - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - -// ============================================================================= - -LRESULT AnimeListDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { - // ListView control - if (idCtrl == IDC_LIST_MAIN || pnmh->hwndFrom == listview.GetHeader()) { - return OnListNotify(reinterpret_cast(pnmh)); - - // Tab control - } else if (idCtrl == IDC_TAB_MAIN) { - return OnTabNotify(reinterpret_cast(pnmh)); - } - - return 0; -} - -void AnimeListDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { - switch (uMsg) { - case WM_SIZE: { - // Set client area - win32::Rect rcWindow(0, 0, size.cx, size.cy); - // Resize tab - rcWindow.left -= 1; - rcWindow.top -= 1; - rcWindow.right += 3; - rcWindow.bottom += 2; - tab.SetPosition(nullptr, rcWindow); - // Resize list - tab.AdjustRect(nullptr, FALSE, &rcWindow); - rcWindow.left -= 3; - rcWindow.top -= 1; - rcWindow.bottom += 2; - listview.SetPosition(nullptr, rcWindow, 0); - } - } -} - -// ============================================================================= - -/* ListView control */ - -AnimeListDialog::ListView::ListView() - : dragging(false), hot_item(-1) { - button_visible[0] = false; - button_visible[1] = false; - button_visible[2] = false; -} - -int AnimeListDialog::ListView::GetSortType(int column) { - switch (column) { - // Progress - case 1: - return LIST_SORTTYPE_PROGRESS; - // Score - case 2: - return LIST_SORTTYPE_NUMBER; - // Season - case 4: - return LIST_SORTTYPE_STARTDATE; - // Other columns - default: - return LIST_SORTTYPE_DEFAULT; - } -} - -void AnimeListDialog::ListView::RefreshItem(int index) { - for (int i = 0; i < 3; i++) { - button_rect[i].SetEmpty(); - button_visible[i] = false; - } - - hot_item = index; - - if (index < 0) { - tooltips.DeleteTip(0); - tooltips.DeleteTip(1); - tooltips.DeleteTip(2); - return; - } - - int anime_id = GetItemParam(index); - auto anime_item = AnimeDatabase.FindItem(anime_id); - - if (!anime_item || !anime_item->IsInList()) - return; - - if (anime_item->GetMyStatus() != mal::MYSTATUS_DROPPED) { - if (anime_item->GetMyStatus() != mal::MYSTATUS_COMPLETED || anime_item->GetMyRewatching()) { - if (anime_item->GetMyLastWatchedEpisode() > 0) - button_visible[0] = true; - if (anime_item->GetEpisodeCount() > anime_item->GetMyLastWatchedEpisode() || - anime_item->GetEpisodeCount() == 0) - button_visible[1] = true; - - win32::Rect rect_item; - GetSubItemRect(index, 1, &rect_item); - rect_item.right -= ScaleX(50); - rect_item.Inflate(-5, -5); - button_rect[0].Copy(rect_item); - button_rect[0].right = button_rect[0].left + rect_item.Height(); - button_rect[1].Copy(rect_item); - button_rect[1].left = button_rect[1].right - rect_item.Height(); - - POINT pt; - ::GetCursorPos(&pt); - ::ScreenToClient(GetWindowHandle(), &pt); - if (rect_item.PtIn(pt)) { - if (anime_item->IsInList()) { - wstring text; - if (anime_item->IsNewEpisodeAvailable()) - AppendString(text, L"#" + ToWstr(anime_item->GetMyLastWatchedEpisode() + 1) + L" is on computer"); - if (anime_item->GetLastAiredEpisodeNumber() > anime_item->GetMyLastWatchedEpisode()) - AppendString(text, L"#" + ToWstr(anime_item->GetLastAiredEpisodeNumber()) + L" is available for download"); - if (!text.empty()) { - tooltips.AddTip(2, text.c_str(), nullptr, &rect_item, false); - } else { - tooltips.DeleteTip(2); - } - } - } else { - tooltips.DeleteTip(2); - } - if ((button_visible[0] && button_rect[0].PtIn(pt)) || - (button_visible[1] && button_rect[1].PtIn(pt))) { - tooltips.AddTip(0, L"-1 episode", nullptr, &button_rect[0], false); - tooltips.AddTip(1, L"+1 episode", nullptr, &button_rect[1], false); - } else { - tooltips.DeleteTip(0); - tooltips.DeleteTip(1); - } - } - } - - button_visible[2] = true; - - win32::Rect rect_item; - GetSubItemRect(index, 2, &rect_item); - rect_item.Inflate(-8, -2); - button_rect[2].Copy(rect_item); -} - -LRESULT AnimeListDialog::ListView::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - // Middle mouse button - case WM_MBUTTONDOWN: { - int item_index = HitTest(); - if (item_index > -1) { - SetSelectedItem(item_index); - int anime_id = parent->GetCurrentId(); - switch (Settings.Program.List.middle_click) { - case 1: - ExecuteAction(L"EditAll", 0, anime_id); - break; - case 2: - ExecuteAction(L"OpenFolder", 0, anime_id); - break; - case 3: - ExecuteAction(L"PlayNext", 0, anime_id); - break; - case 4: - ExecuteAction(L"Info", 0, anime_id); - break; - } - } - break; - } - - // Mouse leave - case WM_MOUSELEAVE: { - int item_index = GetNextItem(-1, LVIS_SELECTED); - if (item_index != hot_item) - RefreshItem(-1); - break; - } - - // Set cursor - case WM_SETCURSOR: { - POINT pt; - ::GetCursorPos(&pt); - ::ScreenToClient(GetWindowHandle(), &pt); - if ((button_visible[0] && button_rect[0].PtIn(pt)) || - (button_visible[1] && button_rect[1].PtIn(pt)) || - (button_visible[2] && button_rect[2].PtIn(pt))) { - SetSharedCursor(IDC_HAND); - return TRUE; - } - break; - } - } - - return WindowProcDefault(hwnd, uMsg, wParam, lParam); -} - -LRESULT AnimeListDialog::OnListNotify(LPARAM lParam) { - LPNMHDR pnmh = reinterpret_cast(lParam); - switch (pnmh->code) { - // Item drag - case LVN_BEGINDRAG: { - POINT pt = {}; - auto lplv = reinterpret_cast(lParam); - listview.drag_image = listview.CreateDragImage(lplv->iItem, &pt); - if (listview.drag_image.GetHandle()) { - pt = lplv->ptAction; - listview.drag_image.BeginDrag(0, 0, 0); - listview.drag_image.DragEnter(MainDialog.GetWindowHandle(), pt.x, pt.y); - listview.dragging = true; - SetCapture(); - } - break; - } - - // Column click - case LVN_COLUMNCLICK: { - auto lplv = reinterpret_cast(lParam); - int order = 1; - if (lplv->iSubItem == listview.GetSortColumn()) order = listview.GetSortOrder() * -1; - listview.Sort(lplv->iSubItem, order, listview.GetSortType(lplv->iSubItem), ListViewCompareProc); - Settings.Program.List.sort_column = lplv->iSubItem; - Settings.Program.List.sort_order = order; - break; - } - - // Delete all items - case LVN_DELETEALLITEMS: { - SetCurrentId(anime::ID_UNKNOWN); - listview.button_visible[0] = false; - listview.button_visible[1] = false; - break; - } - - // Item select - case LVN_ITEMCHANGED: { - auto lplv = reinterpret_cast(lParam); - auto anime_id = static_cast(lplv->lParam); - SetCurrentId(anime_id); - if (lplv->uNewState) - listview.RefreshItem(lplv->iItem); - break; - } - - // Item hover - case LVN_HOTTRACK: { - auto lplv = reinterpret_cast(lParam); - listview.RefreshItem(lplv->iItem); - break; - } - - // Double click - case NM_DBLCLK: { - if (listview.GetSelectedCount() > 0) { - bool on_button = false; - int anime_id = GetCurrentId(); - auto lpnmitem = reinterpret_cast(lParam); - if (listview.button_visible[0] && listview.button_rect[0].PtIn(lpnmitem->ptAction)) { - ExecuteAction(L"DecrementEpisode", 0, anime_id); - on_button = true; - } else if (listview.button_visible[1] && listview.button_rect[1].PtIn(lpnmitem->ptAction)) { - ExecuteAction(L"IncrementEpisode", 0, anime_id); - on_button = true; - } - if (on_button) { - int list_index = GetListIndex(GetCurrentId()); - listview.RefreshItem(list_index); - listview.RedrawItems(list_index, list_index, true); - } else { - switch (Settings.Program.List.double_click) { - case 1: - ExecuteAction(L"EditAll", 0, anime_id); - break; - case 2: - ExecuteAction(L"OpenFolder", 0, anime_id); - break; - case 3: - ExecuteAction(L"PlayNext", 0, anime_id); - break; - case 4: - ExecuteAction(L"Info", 0, anime_id); - break; - } - } - } - break; - } - - // Left click - case NM_CLICK: { - if (pnmh->hwndFrom == listview.GetWindowHandle()) { - if (listview.GetSelectedCount() > 0) { - int anime_id = GetCurrentId(); - auto lpnmitem = reinterpret_cast(lParam); - if (listview.button_visible[0] && listview.button_rect[0].PtIn(lpnmitem->ptAction)) { - ExecuteAction(L"DecrementEpisode", 0, anime_id); - } else if (listview.button_visible[1] && listview.button_rect[1].PtIn(lpnmitem->ptAction)) { - ExecuteAction(L"IncrementEpisode", 0, anime_id); - } else if (listview.button_visible[2] && listview.button_rect[2].PtIn(lpnmitem->ptAction)) { - POINT pt = {listview.button_rect[2].left, listview.button_rect[2].bottom}; - ClientToScreen(listview.GetWindowHandle(), &pt); - UpdateAnimeMenu(GetCurrentItem()); - ExecuteAction(UI.Menus.Show(GetWindowHandle(), pt.x, pt.y, L"EditScore"), 0, anime_id); - } - int list_index = GetListIndex(GetCurrentId()); - listview.RefreshItem(list_index); - listview.RedrawItems(list_index, list_index, true); - } - } - break; - } - - // Right click - case NM_RCLICK: { - if (pnmh->hwndFrom == listview.GetWindowHandle()) { - if (listview.GetSelectedCount() > 0) { - int anime_id = GetCurrentId(); - auto anime_item = GetCurrentItem(); - UpdateAllMenus(anime_item); - int index = listview.HitTest(true); - if (anime_item->IsInList()) { - switch (index) { - // Score - case 2: - ExecuteAction(UI.Menus.Show(g_hMain, 0, 0, L"EditScore"), 0, anime_id); - break; - // Other - default: - ExecuteAction(UI.Menus.Show(g_hMain, 0, 0, L"RightClick"), 0, anime_id); - break; - } - UpdateAllMenus(anime_item); - } else { - UpdateSearchListMenu(true); - ExecuteAction(UI.Menus.Show(g_hMain, 0, 0, L"SearchList"), 0, anime_id); - } - } - } - break; - } - - // Text callback - case LVN_GETDISPINFO: { - NMLVDISPINFO* plvdi = reinterpret_cast(lParam); - auto anime_item = AnimeDatabase.FindItem(static_cast(plvdi->item.lParam)); - if (!anime_item) break; - switch (plvdi->item.iSubItem) { - case 0: // Anime title - if (Settings.Program.List.english_titles) { - plvdi->item.pszText = const_cast( - anime_item->GetEnglishTitle(true).data()); - } else { - plvdi->item.pszText = const_cast( - anime_item->GetTitle().data()); - } - break; - } - break; - } - - // Key press - case LVN_KEYDOWN: { - LPNMLVKEYDOWN pnkd = reinterpret_cast(lParam); - int anime_id = GetCurrentId(); - auto anime_item = GetCurrentItem(); - switch (pnkd->wVKey) { - // Delete item - case VK_DELETE: { - if (listview.GetSelectedCount() > 0) - ExecuteAction(L"EditDelete()", 0, anime_id); - break; - } - default: { - if (listview.GetSelectedCount() > 0 && - GetKeyState(VK_CONTROL) & 0x8000) { - // Edit episode - if (pnkd->wVKey == VK_ADD) { - if (anime_item) { - int value = anime_item->GetMyLastWatchedEpisode(); - ExecuteAction(L"EditEpisode(" + ToWstr(value + 1) + L")", 0, anime_id); - } - } else if (pnkd->wVKey == VK_SUBTRACT) { - if (anime_item) { - int value = anime_item->GetMyLastWatchedEpisode(); - ExecuteAction(L"EditEpisode(" + ToWstr(value - 1) + L")", 0, anime_id); - } - // Edit score - } else if (pnkd->wVKey >= '0' && pnkd->wVKey <= '9') { - ExecuteAction(L"EditScore(" + ToWstr(pnkd->wVKey - '0') + L")", 0, anime_id); - } else if (pnkd->wVKey >= VK_NUMPAD0 && pnkd->wVKey <= VK_NUMPAD9) { - ExecuteAction(L"EditScore(" + ToWstr(pnkd->wVKey - VK_NUMPAD0) + L")", 0, anime_id); - } - } - break; - } - } - break; - } - - // Custom draw - case NM_CUSTOMDRAW: { - return OnListCustomDraw(lParam); - } - } - - return 0; -} - -void AnimeListDialog::ListView::DrawProgressBar(HDC hdc, RECT* rc, int index, - UINT uItemState, anime::Item& anime_item) { - win32::Dc dc = hdc; - win32::Rect rcBar = *rc; - - int eps_aired = anime_item.GetLastAiredEpisodeNumber(true); - int eps_watched = anime_item.GetMyLastWatchedEpisode(true); - int eps_estimate = anime_item.GetEpisodeCount(true); - int eps_total = anime_item.GetEpisodeCount(false); - - if (eps_watched > eps_aired) eps_aired = -1; - if (eps_watched == 0) eps_watched = -1; - - rcBar.right -= ScaleX(50); - - // Draw border - rcBar.Inflate(-4, -4); - UI.list_progress.border.Draw(dc.Get(), &rcBar); - // Draw background - rcBar.Inflate(-1, -1); - UI.list_progress.background.Draw(dc.Get(), &rcBar); - - win32::Rect rcAired = rcBar; - win32::Rect rcAvail = rcBar; - win32::Rect rcButton = rcBar; - win32::Rect rcSeparator = rcBar; - win32::Rect rcWatched = rcBar; - - if (eps_watched > -1 || eps_aired > -1) { - float ratio_aired = 0.0f; - float ratio_watched = 0.0f; - if (eps_estimate) { - if (eps_aired > 0) { - ratio_aired = static_cast(eps_aired) / static_cast(eps_estimate); - } - if (eps_watched > 0) { - ratio_watched = static_cast(eps_watched) / static_cast(eps_estimate); - } - } else { - ratio_aired = eps_aired > -1 ? 0.85f : 0.0f; - ratio_watched = eps_watched > 0 ? 0.8f : 0.0f; - } - if (ratio_watched > 1.0f) { - // The number of watched episodes is greater than the number of total episodes - ratio_watched = 1.0f; - } - - if (eps_aired > -1) { - rcAired.right = static_cast((rcAired.Width()) * ratio_aired) + rcAired.left; - } - if (ratio_watched > -1) { - rcWatched.right = static_cast((rcWatched.Width()) * ratio_watched) + rcWatched.left; - } - - // Draw aired episodes - if (Settings.Program.List.progress_show_aired && eps_aired > 0) { - UI.list_progress.aired.Draw(dc.Get(), &rcAired); - } - - // Draw progress - if (anime_item.GetMyRewatching()) { - UI.list_progress.watching.Draw(dc.Get(), &rcWatched); - } else { - switch (anime_item.GetMyStatus()) { - default: - case mal::MYSTATUS_WATCHING: - UI.list_progress.watching.Draw(dc.Get(), &rcWatched); - break; - case mal::MYSTATUS_COMPLETED: - UI.list_progress.completed.Draw(dc.Get(), &rcWatched); - break; - case mal::MYSTATUS_ONHOLD: - UI.list_progress.onhold.Draw(dc.Get(), &rcWatched); - break; - case mal::MYSTATUS_DROPPED: - UI.list_progress.dropped.Draw(dc.Get(), &rcWatched); - break; - case mal::MYSTATUS_PLANTOWATCH: - UI.list_progress.plantowatch.Draw(dc.Get(), &rcWatched); - break; - } - } - } - - // Draw episode availability - if (Settings.Program.List.progress_show_available) { - if (eps_estimate > 0) { - float width = static_cast(rcBar.Width()) / static_cast(eps_estimate); - int available_episode_count = static_cast(anime_item.GetAvailableEpisodeCount()); - for (int i = eps_watched + 1; i <= available_episode_count; i++) { - if (i > 0 && anime_item.IsEpisodeAvailable(i)) { - rcAvail.left = static_cast(rcBar.left + (width * (i - 1))); - rcAvail.right = static_cast(rcAvail.left + width + 1); - UI.list_progress.available.Draw(dc.Get(), &rcAvail); - } - } - } else { - if (anime_item.IsNewEpisodeAvailable()) { - float ratio_avail = anime_item.IsEpisodeAvailable(eps_aired) ? 0.85f : 0.83f; - rcAvail.right = rcAvail.left + static_cast((rcAvail.Width()) * ratio_avail); - rcAvail.left = rcWatched.right; - UI.list_progress.available.Draw(dc.Get(), &rcAvail); - } - } - } - - // Draw separators - if (eps_watched > 0 && (eps_watched < eps_total || eps_total == 0)) { - rcSeparator.left = rcWatched.right; - rcSeparator.right = rcWatched.right + 1; - UI.list_progress.separator.Draw(dc.Get(), &rcSeparator); - } - if (eps_aired > 0 && (eps_aired < eps_total || eps_total == 0)) { - rcSeparator.left = rcAired.right; - rcSeparator.right = rcAired.right + 1; - UI.list_progress.separator.Draw(dc.Get(), &rcSeparator); - } - - // Draw buttons - if (index > -1 && index == hot_item) { - // Draw decrement button - if (button_visible[0]) { - rcButton = button_rect[0]; - dc.FillRect(rcButton, UI.list_progress.button.value[0]); - rcButton.Inflate(-1, -((button_rect[0].Height() - 1) / 2)); - dc.FillRect(rcButton, UI.list_progress.background.value[0]); - } - // Draw increment button - if (button_visible[1]) { - rcButton = button_rect[1]; - dc.FillRect(rcButton, UI.list_progress.button.value[0]); - rcButton.Inflate(-1, -((button_rect[1].Height() - 1) / 2)); - dc.FillRect(rcButton, UI.list_progress.background.value[0]); - rcButton = button_rect[1]; - rcButton.Inflate(-((button_rect[1].Width() - 1) / 2), -1); - dc.FillRect(rcButton, UI.list_progress.background.value[0]); - } - } - - // Draw text - wstring text; - win32::Rect rcText = *rc; - COLORREF text_color = dc.GetTextColor(); - dc.SetBkMode(TRANSPARENT); - - // Separator - rcText.left = rcBar.right; - dc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); - dc.DrawText(L"/", 1, rcText, - DT_CENTER | DT_VCENTER | DT_SINGLELINE); - dc.SetTextColor(text_color); - - // Episodes watched - text = mal::TranslateNumber(eps_watched, L"0"); - rcText.right -= (rcText.Width() / 2) + 4; - if (eps_watched < 1) { - dc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); - } else if (eps_watched > eps_total && eps_total) { - dc.SetTextColor(::GetSysColor(COLOR_HIGHLIGHT)); - } else if (eps_watched < eps_total && anime_item.GetMyStatus() == mal::MYSTATUS_COMPLETED) { - dc.SetTextColor(::GetSysColor(COLOR_HIGHLIGHT)); - } - dc.DrawText(text.c_str(), text.length(), rcText, - DT_RIGHT | DT_VCENTER | DT_SINGLELINE); - dc.SetTextColor(text_color); - - // Total episodes - text = mal::TranslateNumber(eps_total, L"?"); - rcText.left = rcText.right + 8; - rcText.right = rc->right; - if (eps_total < 1) - dc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); - dc.DrawText(text.c_str(), text.length(), rcText, - DT_LEFT | DT_VCENTER | DT_SINGLELINE); - dc.SetTextColor(text_color); - - // Don't destroy the DC - dc.DetachDC(); -} - -void AnimeListDialog::ListView::DrawScoreBox(HDC hdc, RECT* rc, int index, - UINT uItemState, anime::Item& anime_item) { - win32::Dc dc = hdc; - win32::Rect rcBox = button_rect[2]; - - if (index > -1 && index == hot_item) { - rcBox.right -= 2; - UI.list_progress.border.Draw(dc.Get(), &rcBox); - rcBox.Inflate(-1, -1); - UI.list_progress.background.Draw(dc.Get(), &rcBox); - rcBox.Inflate(-4, 0); - - COLORREF text_color = dc.GetTextColor(); - dc.SetBkMode(TRANSPARENT); - - wstring text = mal::TranslateNumber(anime_item.GetMyScore()); - dc.SetTextColor(::GetSysColor(COLOR_WINDOWTEXT)); - dc.DrawText(text.c_str(), text.length(), rcBox, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - - dc.EditFont(nullptr, 5); - dc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); - dc.DrawText(L"\u25BC", 1, rcBox, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); - dc.SetTextColor(text_color); - } - - dc.DetachDC(); -} - -LRESULT AnimeListDialog::OnListCustomDraw(LPARAM lParam) { - LPNMLVCUSTOMDRAW pCD = reinterpret_cast(lParam); - - switch (pCD->nmcd.dwDrawStage) { - case CDDS_PREPAINT: - return CDRF_NOTIFYITEMDRAW; - case CDDS_ITEMPREPAINT: - return CDRF_NOTIFYSUBITEMDRAW; - case CDDS_PREERASE: - case CDDS_ITEMPREERASE: - return CDRF_NOTIFYPOSTERASE; - - case CDDS_ITEMPREPAINT | CDDS_SUBITEM: { - auto anime_item = AnimeDatabase.FindItem(static_cast(pCD->nmcd.lItemlParam)); - // Alternate background color - if ((pCD->nmcd.dwItemSpec % 2) && !listview.IsGroupViewEnabled()) - pCD->clrTextBk = ChangeColorBrightness(GetSysColor(COLOR_WINDOW), -0.03f); - // Change text color - if (!anime_item) return CDRF_NOTIFYPOSTPAINT; - pCD->clrText = GetSysColor(COLOR_WINDOWTEXT); - switch (pCD->iSubItem) { - case 0: - if (anime_item->IsNewEpisodeAvailable() && Settings.Program.List.highlight) - pCD->clrText = GetSysColor(COLOR_HIGHLIGHT); - break; - case 2: - if (!anime_item->GetMyScore()) - pCD->clrText = GetSysColor(COLOR_GRAYTEXT); - break; - } - // Indicate currently playing - if (anime_item->GetPlaying()) { - pCD->clrTextBk = theme::COLOR_LIGHTGREEN; - static HFONT hFontDefault = ChangeDCFont(pCD->nmcd.hdc, nullptr, -1, true, -1, -1); - static HFONT hFontBold = reinterpret_cast(GetCurrentObject(pCD->nmcd.hdc, OBJ_FONT)); - SelectObject(pCD->nmcd.hdc, pCD->iSubItem == 0 ? hFontBold : hFontDefault); - return CDRF_NEWFONT | CDRF_NOTIFYPOSTPAINT; - } - return CDRF_NOTIFYPOSTPAINT; - } - - case CDDS_ITEMPOSTPAINT | CDDS_SUBITEM: { - auto anime_item = AnimeDatabase.FindItem(static_cast(pCD->nmcd.lItemlParam)); - if (!anime_item) return CDRF_DODEFAULT; - if (pCD->iSubItem == 1 || pCD->iSubItem == 2) { - win32::Rect rcItem; - listview.GetSubItemRect(pCD->nmcd.dwItemSpec, pCD->iSubItem, &rcItem); - if (!rcItem.IsEmpty()) { - if (pCD->iSubItem == 1) { - listview.DrawProgressBar(pCD->nmcd.hdc, &rcItem, pCD->nmcd.dwItemSpec, - pCD->nmcd.uItemState, *anime_item); - } else if (pCD->iSubItem == 2) { - listview.DrawScoreBox(pCD->nmcd.hdc, &rcItem, pCD->nmcd.dwItemSpec, - pCD->nmcd.uItemState, *anime_item); - } - } - } - return CDRF_DODEFAULT; - } - - default: { - return CDRF_DODEFAULT; - } - } -} - -// ============================================================================= - -/* Tab control */ - -LRESULT AnimeListDialog::OnTabNotify(LPARAM lParam) { - switch (reinterpret_cast(lParam)->code) { - // Tab select - case TCN_SELCHANGE: { - int tab_index = tab.GetCurrentlySelected(); - int index = static_cast(tab.GetItemParam(tab_index)); - RefreshList(index); - break; - } - } - - return 0; -} - -// ============================================================================= - -int AnimeListDialog::GetCurrentId() { - if (current_id_ > anime::ID_UNKNOWN) - if (!AnimeDatabase.FindItem(current_id_)) - current_id_ = anime::ID_UNKNOWN; - - return current_id_; -} - -anime::Item* AnimeListDialog::GetCurrentItem() { - anime::Item* item = nullptr; - - if (current_id_ > anime::ID_UNKNOWN) { - item = AnimeDatabase.FindItem(current_id_); - if (!item) current_id_ = anime::ID_UNKNOWN; - } - - return item; -} - -void AnimeListDialog::SetCurrentId(int anime_id) { - if (anime_id > anime::ID_UNKNOWN) - if (!AnimeDatabase.FindItem(anime_id)) - anime_id = anime::ID_UNKNOWN; - - current_id_ = anime_id; -} - -int AnimeListDialog::GetListIndex(int anime_id) { - if (IsWindow()) - for (int i = 0; i < listview.GetItemCount(); i++) - if (static_cast(listview.GetItemParam(i)) == anime_id) - return i; - - return -1; -} - -void AnimeListDialog::RefreshList(int index) { - if (!IsWindow()) return; - - // Remember current status - if (index > mal::MYSTATUS_NOTINLIST) - current_status_ = index; - - // Hide list to avoid visual defects and gain performance - listview.Hide(); - listview.DeleteAllItems(); - listview.RefreshItem(-1); - - // Enable group view - bool group_view = !MainDialog.search_bar.filters.text.empty() && - win32::GetWinVersion() > win32::VERSION_XP; - listview.EnableGroupView(group_view); - - // Add items to list - vector group_count(7); - int group_index = -1; - int icon_index = 0; - int i = 0; - for (auto it = AnimeDatabase.items.begin(); it != AnimeDatabase.items.end(); ++it) { - anime::Item& anime_item = it->second; - - if (!anime_item.IsInList()) - continue; - if (!group_view) - if (current_status_ != anime_item.GetMyStatus()) - if (current_status_ != mal::MYSTATUS_WATCHING || !it->second.GetMyRewatching()) - continue; - if (!MainDialog.search_bar.filters.CheckItem(anime_item)) - continue; - - group_count.at(anime_item.GetMyStatus())++; - group_index = group_view ? anime_item.GetMyStatus() : -1; - icon_index = anime_item.GetPlaying() ? ICON16_PLAY : StatusToIcon(anime_item.GetAiringStatus()); - i = listview.GetItemCount(); - - listview.InsertItem(i, group_index, icon_index, - 0, nullptr, LPSTR_TEXTCALLBACK, - static_cast(anime_item.GetId())); - listview.SetItem(i, 2, mal::TranslateNumber(anime_item.GetMyScore()).c_str()); - listview.SetItem(i, 3, mal::TranslateType(anime_item.GetType()).c_str()); - listview.SetItem(i, 4, mal::TranslateDateToSeason(anime_item.GetDate(anime::DATE_START)).c_str()); - } - - // Set group headers - if (group_view) { - for (int i = mal::MYSTATUS_NOTINLIST; i <= mal::MYSTATUS_PLANTOWATCH; i++) { - if (i != mal::MYSTATUS_UNKNOWN) { - wstring text = mal::TranslateMyStatus(i, false); - text += group_count.at(i) > 0 ? L" (" + ToWstr(group_count.at(i)) + L")" : L""; - listview.SetGroupText(i, text.c_str()); - } - } - } - - // Sort items - listview.Sort(listview.GetSortColumn(), - listview.GetSortOrder(), - listview.GetSortType(listview.GetSortColumn()), - ListViewCompareProc); - - // Show again - listview.Show(SW_SHOW); -} - -void AnimeListDialog::RefreshListItem(int anime_id) { - int index = GetListIndex(anime_id); - if (index > -1) { - auto anime_item = AnimeDatabase.FindItem(anime_id); - listview.SetItem(index, 2, mal::TranslateNumber(anime_item->GetMyScore()).c_str()); - listview.RedrawItems(index, index, true); - } -} - -void AnimeListDialog::RefreshTabs(int index) { - if (!IsWindow()) return; - - // Remember last index - if (index > mal::MYSTATUS_NOTINLIST) - current_status_ = index; - - // Hide - tab.Hide(); - - // Refresh text - for (int i = 1; i <= 6; i++) - if (i != 5) - tab.SetItemText(i == 6 ? 4 : i - 1, mal::TranslateMyStatus(i, true).c_str()); - - // Select related tab - bool group_view = !MainDialog.search_bar.filters.text.empty(); - int tab_index = current_status_; - if (group_view) { - tab_index = -1; - } else if (tab_index == 6) { - tab_index = 4; - } else { - tab_index--; - } - tab.SetCurrentlySelected(tab_index); - - // Show again - tab.Show(SW_SHOW); -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "base/gfx.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/anime_filter.h" +#include "library/anime_util.h" +#include "library/resource.h" +#include "taiga/resource.h" +#include "taiga/script.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" +#include "ui/dlg/dlg_anime_list.h" +#include "ui/dlg/dlg_main.h" +#include "ui/dlg/dlg_torrent.h" +#include "ui/dialog.h" +#include "ui/list.h" +#include "ui/menu.h" +#include "ui/theme.h" +#include "ui/ui.h" +#include "win/win_gdi.h" + +namespace ui { + +AnimeListDialog DlgAnimeList; + +AnimeListDialog::AnimeListDialog() + : current_id_(anime::ID_UNKNOWN), + current_status_(anime::kWatching) { +} + +BOOL AnimeListDialog::OnInitDialog() { + // Create tab control + tab.Attach(GetDlgItem(IDC_TAB_MAIN)); + + // Create main list + listview.parent = this; + listview.Attach(GetDlgItem(IDC_LIST_MAIN)); + listview.SetExtendedStyle(LVS_EX_AUTOSIZECOLUMNS | + LVS_EX_DOUBLEBUFFER | + LVS_EX_FULLROWSELECT | + LVS_EX_INFOTIP | + LVS_EX_LABELTIP | + LVS_EX_TRACKSELECT); + listview.SetHoverTime(60 * 1000); + listview.SetImageList(ui::Theme.GetImageList16().GetHandle()); + listview.Sort(Settings.GetInt(taiga::kApp_List_SortColumn), + Settings.GetInt(taiga::kApp_List_SortOrder), + ui::kListSortDefault, + ui::ListViewCompareProc); + listview.SetTheme(); + + // Create list tooltips + listview.tooltips.Create(listview.GetWindowHandle()); + listview.tooltips.SetDelayTime(30000, -1, 0); + + // Insert list columns + listview.InsertColumn(0, GetSystemMetrics(SM_CXSCREEN), 340, LVCFMT_LEFT, L"Anime title"); + listview.InsertColumn(1, 200, 200, LVCFMT_CENTER, L"Progress"); + listview.InsertColumn(2, 62, 62, LVCFMT_CENTER, L"Score"); + listview.InsertColumn(3, 62, 62, LVCFMT_CENTER, L"Type"); + listview.InsertColumn(4, 105, 105, LVCFMT_RIGHT, L"Season"); + + // Insert tabs and list groups + listview.InsertGroup(anime::kNotInList, anime::TranslateMyStatus(anime::kNotInList, false).c_str()); + for (int i = anime::kMyStatusFirst; i < anime::kMyStatusLast; i++) { + tab.InsertItem(i - 1, anime::TranslateMyStatus(i, true).c_str(), (LPARAM)i); + listview.InsertGroup(i, anime::TranslateMyStatus(i, false).c_str()); + } + + // Track mouse leave event for the list view + TRACKMOUSEEVENT tme = {0}; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = listview.GetWindowHandle(); + TrackMouseEvent(&tme); + + // Refresh + RefreshList(anime::kWatching); + RefreshTabs(anime::kWatching); + + // Success + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// + +INT_PTR AnimeListDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_MOUSEMOVE: { + // Drag list item + if (listview.dragging) { + bool allow_drop = false; + + if (tab.HitTest() > -1) + allow_drop = true; + + if (!allow_drop) { + POINT pt; + GetCursorPos(&pt); + win::Rect rect_edit; + DlgMain.edit.GetWindowRect(&rect_edit); + if (rect_edit.PtIn(pt)) + allow_drop = true; + } + + if (!allow_drop) { + TVHITTESTINFO ht = {0}; + DlgMain.treeview.HitTest(&ht, true); + if (ht.flags & TVHT_ONITEM) { + int index = DlgMain.treeview.GetItemData(ht.hItem); + switch (index) { + case kSidebarItemSearch: + case kSidebarItemFeeds: + allow_drop = true; + break; + } + } + } + + POINT pt; + GetCursorPos(&pt); + ::ScreenToClient(DlgMain.GetWindowHandle(), &pt); + listview.drag_image.DragMove(pt.x + 16, pt.y + 32); + SetSharedCursor(allow_drop ? IDC_ARROW : IDC_NO); + } + break; + } + + case WM_LBUTTONUP: { + // Drop list item + if (listview.dragging) { + listview.drag_image.DragLeave(DlgMain.GetWindowHandle()); + listview.drag_image.EndDrag(); + listview.drag_image.Destroy(); + listview.dragging = false; + ReleaseCapture(); + + int anime_id = GetCurrentId(); + auto anime_item = GetCurrentItem(); + if (!anime_item) + break; + + int tab_index = tab.HitTest(); + if (tab_index > -1) { + int status = tab.GetItemParam(tab_index); + if (anime_item->IsInList()) { + ExecuteAction(L"EditStatus(" + ToWstr(status) + L")", 0, anime_id); + } else { + AnimeDatabase.AddToList(anime_id, status); + } + break; + } + + std::wstring text = Settings.GetBool(taiga::kApp_List_DisplayEnglishTitles) ? + anime_item->GetEnglishTitle(true) : anime_item->GetTitle(); + + POINT pt; + GetCursorPos(&pt); + win::Rect rect_edit; + DlgMain.edit.GetWindowRect(&rect_edit); + if (rect_edit.PtIn(pt)) { + DlgMain.edit.SetText(text); + break; + } + + TVHITTESTINFO ht = {0}; + DlgMain.treeview.HitTest(&ht, true); + if (ht.flags & TVHT_ONITEM) { + int index = DlgMain.treeview.GetItemData(ht.hItem); + switch (index) { + case kSidebarItemSearch: + ExecuteAction(L"SearchAnime(" + text + L")"); + break; + case kSidebarItemFeeds: + DlgTorrent.Search(Settings[taiga::kTorrent_Discovery_SearchUrl], anime_id); + break; + } + } + } + break; + } + + case WM_MEASUREITEM: { + if (wParam == IDC_LIST_MAIN) { + auto mis = reinterpret_cast(lParam); + mis->itemHeight = 48; + return TRUE; + } + break; + } + + case WM_DRAWITEM: { + if (wParam == IDC_LIST_MAIN) { + auto dis = reinterpret_cast(lParam); + win::Dc dc = dis->hDC; + win::Rect rect = dis->rcItem; + + int anime_id = dis->itemData; + auto anime_item = AnimeDatabase.FindItem(anime_id); + if (!anime_item) + return TRUE; + + if ((dis->itemState & ODS_SELECTED) == ODS_SELECTED) + dc.FillRect(rect, ui::kColorLightBlue); + rect.Inflate(-2, -2); + dc.FillRect(rect, ui::kColorLightGray); + + // Draw image + win::Rect rect_image = rect; + rect_image.right = rect_image.left + static_cast(rect_image.Height() / 1.4); + dc.FillRect(rect_image, ui::kColorGray); + if (ImageDatabase.Load(anime_id, false, false)) { + auto image = ImageDatabase.GetImage(anime_id); + int sbm = dc.SetStretchBltMode(HALFTONE); + dc.StretchBlt(rect_image.left, rect_image.top, + rect_image.Width(), rect_image.Height(), + image->dc.Get(), 0, 0, + image->rect.Width(), + image->rect.Height(), + SRCCOPY); + dc.SetStretchBltMode(sbm); + } + + // Draw title + rect.left += rect_image.Width() + 8; + int bk_mode = dc.SetBkMode(TRANSPARENT); + dc.AttachFont(ui::Theme.GetHeaderFont()); + dc.DrawText(anime_item->GetTitle().c_str(), anime_item->GetTitle().length(), rect, + DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE); + dc.DetachFont(); + + // Draw second line of information + rect.top += 20; + COLORREF text_color = dc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); + std::wstring text = ToWstr(anime_item->GetMyLastWatchedEpisode()) + L"/" + + ToWstr(anime_item->GetEpisodeCount()); + dc.DrawText(text.c_str(), -1, rect, + DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE); + dc.SetTextColor(text_color); + dc.SetBkMode(bk_mode); + + // Draw progress bar + rect.left -= 2; + rect.top += 12; + rect.bottom = rect.top + 12; + rect.right -= 8; + listview.DrawProgressBar(dc.Get(), &rect, dis->itemID, 0, *anime_item); + + dc.DetachDc(); + return TRUE; + } + break; + } + + // Forward mouse wheel messages to the list + case WM_MOUSEWHEEL: { + return listview.SendMessage(uMsg, wParam, lParam); + } + } + + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + +//////////////////////////////////////////////////////////////////////////////// + +LRESULT AnimeListDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { + // ListView control + if (idCtrl == IDC_LIST_MAIN || pnmh->hwndFrom == listview.GetHeader()) { + return OnListNotify(reinterpret_cast(pnmh)); + + // Tab control + } else if (idCtrl == IDC_TAB_MAIN) { + return OnTabNotify(reinterpret_cast(pnmh)); + } + + return 0; +} + +void AnimeListDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { + switch (uMsg) { + case WM_SIZE: { + // Set client area + win::Rect rcWindow(0, 0, size.cx, size.cy); + // Resize tab + rcWindow.left -= 1; + rcWindow.top -= 1; + rcWindow.right += 3; + rcWindow.bottom += 2; + tab.SetPosition(nullptr, rcWindow); + // Resize list + tab.AdjustRect(nullptr, FALSE, &rcWindow); + rcWindow.left -= 3; + rcWindow.top -= 1; + rcWindow.bottom += 2; + listview.SetPosition(nullptr, rcWindow, 0); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +/* ListView control */ + +AnimeListDialog::ListView::ListView() + : dragging(false), hot_item(-1), parent(nullptr) { + button_visible[0] = false; + button_visible[1] = false; + button_visible[2] = false; +} + +int AnimeListDialog::ListView::GetSortType(int column) { + switch (column) { + // Progress + case 1: + return ui::kListSortProgress; + // Score + case 2: + return ui::kListSortNumber; + // Season + case 4: + return ui::kListSortSeason; + // Other columns + default: + return ui::kListSortDefault; + } +} + +void AnimeListDialog::ListView::RefreshItem(int index) { + for (int i = 0; i < 3; i++) { + button_rect[i].SetEmpty(); + button_visible[i] = false; + } + + hot_item = index; + + if (index < 0) { + tooltips.DeleteTip(0); + tooltips.DeleteTip(1); + tooltips.DeleteTip(2); + return; + } + + int anime_id = GetItemParam(index); + auto anime_item = AnimeDatabase.FindItem(anime_id); + + if (!anime_item || !anime_item->IsInList()) + return; + + if (anime_item->GetMyStatus() != anime::kDropped) { + if (anime_item->GetMyStatus() != anime::kCompleted || + anime_item->GetMyRewatching()) { + if (anime_item->GetMyLastWatchedEpisode() > 0) + button_visible[0] = true; + if (anime_item->GetEpisodeCount() > anime_item->GetMyLastWatchedEpisode() || + anime_item->GetEpisodeCount() == 0) + button_visible[1] = true; + + win::Rect rect_item; + GetSubItemRect(index, 1, &rect_item); + rect_item.right -= ScaleX(50); + rect_item.Inflate(-5, -5); + button_rect[0].Copy(rect_item); + button_rect[0].right = button_rect[0].left + rect_item.Height(); + button_rect[1].Copy(rect_item); + button_rect[1].left = button_rect[1].right - rect_item.Height(); + + POINT pt; + ::GetCursorPos(&pt); + ::ScreenToClient(GetWindowHandle(), &pt); + if (rect_item.PtIn(pt)) { + if (anime_item->IsInList()) { + std::wstring text; + if (anime_item->IsNewEpisodeAvailable()) + AppendString(text, L"#" + ToWstr(anime_item->GetMyLastWatchedEpisode() + 1) + L" is on computer"); + if (anime_item->GetLastAiredEpisodeNumber() > anime_item->GetMyLastWatchedEpisode()) + AppendString(text, L"#" + ToWstr(anime_item->GetLastAiredEpisodeNumber()) + L" is available for download"); + if (!text.empty()) { + tooltips.AddTip(2, text.c_str(), nullptr, &rect_item, false); + } else { + tooltips.DeleteTip(2); + } + } + } else { + tooltips.DeleteTip(2); + } + if ((button_visible[0] && button_rect[0].PtIn(pt)) || + (button_visible[1] && button_rect[1].PtIn(pt))) { + tooltips.AddTip(0, L"-1 episode", nullptr, &button_rect[0], false); + tooltips.AddTip(1, L"+1 episode", nullptr, &button_rect[1], false); + } else { + tooltips.DeleteTip(0); + tooltips.DeleteTip(1); + } + } + } + + button_visible[2] = true; + + win::Rect rect_item; + GetSubItemRect(index, 2, &rect_item); + rect_item.Inflate(-8, -2); + button_rect[2].Copy(rect_item); +} + +LRESULT AnimeListDialog::ListView::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + // Middle mouse button + case WM_MBUTTONDOWN: { + int item_index = HitTest(); + if (item_index > -1) { + SetSelectedItem(item_index); + int anime_id = parent->GetCurrentId(); + switch (Settings.GetInt(taiga::kApp_List_MiddleClickAction)) { + case 1: + ShowDlgAnimeEdit(anime_id); + break; + case 2: + ExecuteAction(L"OpenFolder", 0, anime_id); + break; + case 3: + anime::PlayNextEpisode(anime_id); + break; + case 4: + ShowDlgAnimeInfo(anime_id); + break; + } + } + break; + } + + // Mouse leave + case WM_MOUSELEAVE: { + int item_index = GetNextItem(-1, LVIS_SELECTED); + if (item_index != hot_item) + RefreshItem(-1); + break; + } + + // Set cursor + case WM_SETCURSOR: { + POINT pt; + ::GetCursorPos(&pt); + ::ScreenToClient(GetWindowHandle(), &pt); + if ((button_visible[0] && button_rect[0].PtIn(pt)) || + (button_visible[1] && button_rect[1].PtIn(pt)) || + (button_visible[2] && button_rect[2].PtIn(pt))) { + SetSharedCursor(IDC_HAND); + return TRUE; + } + break; + } + } + + return WindowProcDefault(hwnd, uMsg, wParam, lParam); +} + +LRESULT AnimeListDialog::OnListNotify(LPARAM lParam) { + LPNMHDR pnmh = reinterpret_cast(lParam); + + switch (pnmh->code) { + // Item drag + case LVN_BEGINDRAG: { + POINT pt = {}; + auto lplv = reinterpret_cast(lParam); + listview.drag_image = listview.CreateDragImage(lplv->iItem, &pt); + if (listview.drag_image.GetHandle()) { + pt = lplv->ptAction; + listview.drag_image.BeginDrag(0, 0, 0); + listview.drag_image.DragEnter(DlgMain.GetWindowHandle(), pt.x, pt.y); + listview.dragging = true; + SetCapture(); + } + break; + } + + // Column click + case LVN_COLUMNCLICK: { + auto lplv = reinterpret_cast(lParam); + int order = 1; + if (lplv->iSubItem == listview.GetSortColumn()) + order = listview.GetSortOrder() * -1; + listview.Sort(lplv->iSubItem, order, listview.GetSortType(lplv->iSubItem), ui::ListViewCompareProc); + Settings.Set(taiga::kApp_List_SortColumn, lplv->iSubItem); + Settings.Set(taiga::kApp_List_SortOrder, order); + break; + } + + // Delete all items + case LVN_DELETEALLITEMS: { + SetCurrentId(anime::ID_UNKNOWN); + listview.button_visible[0] = false; + listview.button_visible[1] = false; + break; + } + + // Item select + case LVN_ITEMCHANGED: { + auto lplv = reinterpret_cast(lParam); + auto anime_id = static_cast(lplv->lParam); + SetCurrentId(anime_id); + if (lplv->uNewState) + listview.RefreshItem(lplv->iItem); + break; + } + + // Item hover + case LVN_HOTTRACK: { + auto lplv = reinterpret_cast(lParam); + listview.RefreshItem(lplv->iItem); + break; + } + + // Double click + case NM_DBLCLK: { + if (listview.GetSelectedCount() > 0) { + bool on_button = false; + int anime_id = GetCurrentId(); + auto lpnmitem = reinterpret_cast(lParam); + if (listview.button_visible[0] && + listview.button_rect[0].PtIn(lpnmitem->ptAction)) { + anime::DecrementEpisode(anime_id); + on_button = true; + } else if (listview.button_visible[1] && + listview.button_rect[1].PtIn(lpnmitem->ptAction)) { + anime::IncrementEpisode(anime_id); + on_button = true; + } + if (on_button) { + int list_index = GetListIndex(GetCurrentId()); + listview.RefreshItem(list_index); + listview.RedrawItems(list_index, list_index, true); + } else { + switch (Settings.GetInt(taiga::kApp_List_DoubleClickAction)) { + case 1: + ShowDlgAnimeEdit(anime_id); + break; + case 2: + ExecuteAction(L"OpenFolder", 0, anime_id); + break; + case 3: + anime::PlayNextEpisode(anime_id); + break; + case 4: + ShowDlgAnimeInfo(anime_id); + break; + } + } + } + break; + } + + // Left click + case NM_CLICK: { + if (pnmh->hwndFrom == listview.GetWindowHandle()) { + if (listview.GetSelectedCount() > 0) { + int anime_id = GetCurrentId(); + auto lpnmitem = reinterpret_cast(lParam); + if (listview.button_visible[0] && + listview.button_rect[0].PtIn(lpnmitem->ptAction)) { + anime::DecrementEpisode(anime_id); + } else if (listview.button_visible[1] && + listview.button_rect[1].PtIn(lpnmitem->ptAction)) { + anime::IncrementEpisode(anime_id); + } else if (listview.button_visible[2] && + listview.button_rect[2].PtIn(lpnmitem->ptAction)) { + POINT pt = {listview.button_rect[2].left, listview.button_rect[2].bottom}; + ClientToScreen(listview.GetWindowHandle(), &pt); + ui::Menus.UpdateAnime(GetCurrentItem()); + ExecuteAction(ui::Menus.Show(GetWindowHandle(), pt.x, pt.y, L"EditScore"), 0, anime_id); + } + int list_index = GetListIndex(GetCurrentId()); + listview.RefreshItem(list_index); + listview.RedrawItems(list_index, list_index, true); + } + } + break; + } + + // Right click + case NM_RCLICK: { + if (pnmh->hwndFrom == listview.GetWindowHandle()) { + if (listview.GetSelectedCount() > 0) { + int anime_id = GetCurrentId(); + auto anime_item = GetCurrentItem(); + ui::Menus.UpdateAll(anime_item); + int index = listview.HitTest(true); + if (anime_item->IsInList()) { + switch (index) { + // Score + case 2: + ExecuteAction(ui::Menus.Show(DlgMain.GetWindowHandle(), 0, 0, L"EditScore"), 0, anime_id); + break; + // Other + default: + ExecuteAction(ui::Menus.Show(DlgMain.GetWindowHandle(), 0, 0, L"RightClick"), 0, anime_id); + break; + } + ui::Menus.UpdateAll(anime_item); + } else { + ui::Menus.UpdateSearchList(true); + ExecuteAction(ui::Menus.Show(DlgMain.GetWindowHandle(), 0, 0, L"SearchList"), 0, anime_id); + } + } + } + break; + } + + // Text callback + case LVN_GETDISPINFO: { + NMLVDISPINFO* plvdi = reinterpret_cast(lParam); + auto anime_item = AnimeDatabase.FindItem(static_cast(plvdi->item.lParam)); + if (!anime_item) + break; + switch (plvdi->item.iSubItem) { + case 0: // Anime title + if (Settings.GetBool(taiga::kApp_List_DisplayEnglishTitles)) { + plvdi->item.pszText = const_cast( + anime_item->GetEnglishTitle(true).data()); + } else { + plvdi->item.pszText = const_cast( + anime_item->GetTitle().data()); + } + break; + } + break; + } + + // Key press + case LVN_KEYDOWN: { + LPNMLVKEYDOWN pnkd = reinterpret_cast(lParam); + int anime_id = GetCurrentId(); + auto anime_item = GetCurrentItem(); + switch (pnkd->wVKey) { + // Delete item + case VK_DELETE: { + if (listview.GetSelectedCount() > 0) + ExecuteAction(L"EditDelete()", 0, anime_id); + break; + } + default: { + if (listview.GetSelectedCount() > 0 && + GetKeyState(VK_CONTROL) & 0x8000) { + // Edit episode + if (pnkd->wVKey == VK_ADD) { + if (anime_item) { + int value = anime_item->GetMyLastWatchedEpisode(); + ExecuteAction(L"EditEpisode(" + ToWstr(value + 1) + L")", 0, anime_id); + } + } else if (pnkd->wVKey == VK_SUBTRACT) { + if (anime_item) { + int value = anime_item->GetMyLastWatchedEpisode(); + ExecuteAction(L"EditEpisode(" + ToWstr(value - 1) + L")", 0, anime_id); + } + // Edit score + } else if (pnkd->wVKey >= '0' && pnkd->wVKey <= '9') { + ExecuteAction(L"EditScore(" + ToWstr(pnkd->wVKey - '0') + L")", 0, anime_id); + } else if (pnkd->wVKey >= VK_NUMPAD0 && pnkd->wVKey <= VK_NUMPAD9) { + ExecuteAction(L"EditScore(" + ToWstr(pnkd->wVKey - VK_NUMPAD0) + L")", 0, anime_id); + } + } + break; + } + } + break; + } + + // Custom draw + case NM_CUSTOMDRAW: { + return OnListCustomDraw(lParam); + } + } + + return 0; +} + +void AnimeListDialog::ListView::DrawProgressBar(HDC hdc, RECT* rc, int index, + UINT uItemState, anime::Item& anime_item) { + win::Dc dc = hdc; + win::Rect rcBar = *rc; + + int eps_aired = anime_item.GetLastAiredEpisodeNumber(true); + int eps_watched = anime_item.GetMyLastWatchedEpisode(true); + int eps_estimate = anime::EstimateEpisodeCount(anime_item); + int eps_total = anime_item.GetEpisodeCount(); + + if (eps_watched > eps_aired) + eps_aired = -1; + if (eps_watched == 0) + eps_watched = -1; + + rcBar.right -= ScaleX(50); + + // Draw border + rcBar.Inflate(-4, -4); + ui::Theme.DrawListProgress(dc.Get(), &rcBar, ui::kListProgressBorder); + // Draw background + rcBar.Inflate(-1, -1); + ui::Theme.DrawListProgress(dc.Get(), &rcBar, ui::kListProgressBackground); + + win::Rect rcAired = rcBar; + win::Rect rcAvail = rcBar; + win::Rect rcButton = rcBar; + win::Rect rcSeparator = rcBar; + win::Rect rcWatched = rcBar; + + if (eps_watched > -1 || eps_aired > -1) { + float ratio_aired = 0.0f; + float ratio_watched = 0.0f; + if (eps_estimate) { + if (eps_aired > 0) { + ratio_aired = static_cast(eps_aired) / static_cast(eps_estimate); + } + if (eps_watched > 0) { + ratio_watched = static_cast(eps_watched) / static_cast(eps_estimate); + } + } else { + ratio_aired = eps_aired > -1 ? 0.85f : 0.0f; + ratio_watched = eps_watched > 0 ? 0.8f : 0.0f; + } + if (ratio_watched > 1.0f) { + // The number of watched episodes is greater than the number of total episodes + ratio_watched = 1.0f; + } + + if (eps_aired > -1) { + rcAired.right = static_cast((rcAired.Width()) * ratio_aired) + rcAired.left; + } + if (ratio_watched > -1) { + rcWatched.right = static_cast((rcWatched.Width()) * ratio_watched) + rcWatched.left; + } + + // Draw aired episodes + if (Settings.GetBool(taiga::kApp_List_ProgressDisplayAired) && eps_aired > 0) { + ui::Theme.DrawListProgress(dc.Get(), &rcAired, ui::kListProgressAired); + } + + // Draw progress + if (anime_item.GetMyStatus() == anime::kWatching || anime_item.GetMyRewatching()) { + ui::Theme.DrawListProgress(dc.Get(), &rcWatched, ui::kListProgressWatching); // Watching + } else if (anime_item.GetMyStatus() == anime::kCompleted) { + ui::Theme.DrawListProgress(dc.Get(), &rcWatched, ui::kListProgressCompleted); // Completed + } else if (anime_item.GetMyStatus() == anime::kDropped) { + ui::Theme.DrawListProgress(dc.Get(), &rcWatched, ui::kListProgressDropped); // Dropped + } else { + ui::Theme.DrawListProgress(dc.Get(), &rcWatched, ui::kListProgressCompleted); // Completed / On hold / Plan to watch + } + // Draw progress + if (anime_item.GetMyRewatching()) { + ui::Theme.DrawListProgress(dc.Get(), &rcWatched, ui::kListProgressWatching); + } else { + switch (anime_item.GetMyStatus()) { + default: + case anime::kWatching: + ui::Theme.DrawListProgress(dc.Get(), &rcWatched, ui::kListProgressWatching); + break; + case anime::kCompleted: + ui::Theme.DrawListProgress(dc.Get(), &rcWatched, ui::kListProgressCompleted); + break; + case anime::kOnHold: + ui::Theme.DrawListProgress(dc.Get(), &rcWatched, ui::kListProgressOnHold); + break; + case anime::kDropped: + ui::Theme.DrawListProgress(dc.Get(), &rcWatched, ui::kListProgressDropped); + break; + case anime::kPlanToWatch: + ui::Theme.DrawListProgress(dc.Get(), &rcWatched, ui::kListProgressPlanToWatch); + break; + } + } + } + + // Draw episode availability + if (Settings.GetBool(taiga::kApp_List_ProgressDisplayAvailable)) { + if (eps_estimate > 0) { + float width = static_cast(rcBar.Width()) / static_cast(eps_estimate); + int available_episode_count = static_cast(anime_item.GetAvailableEpisodeCount()); + for (int i = eps_watched + 1; i <= available_episode_count; i++) { + if (i > 0 && anime_item.IsEpisodeAvailable(i)) { + rcAvail.left = static_cast(rcBar.left + (width * (i - 1))); + rcAvail.right = static_cast(rcAvail.left + width + 1); + ui::Theme.DrawListProgress(dc.Get(), &rcAvail, ui::kListProgressAvailable); + } + } + } else { + if (anime_item.IsNewEpisodeAvailable()) { + float ratio_avail = anime_item.IsEpisodeAvailable(eps_aired) ? 0.85f : 0.83f; + rcAvail.right = rcAvail.left + static_cast((rcAvail.Width()) * ratio_avail); + rcAvail.left = rcWatched.right; + ui::Theme.DrawListProgress(dc.Get(), &rcAvail, ui::kListProgressAvailable); + } + } + } + + // Draw separators + if (eps_watched > 0 && (eps_watched < eps_total || eps_total == 0)) { + rcSeparator.left = rcWatched.right; + rcSeparator.right = rcWatched.right + 1; + ui::Theme.DrawListProgress(dc.Get(), &rcSeparator, ui::kListProgressSeparator); + } + if (eps_aired > 0 && (eps_aired < eps_total || eps_total == 0)) { + rcSeparator.left = rcAired.right; + rcSeparator.right = rcAired.right + 1; + ui::Theme.DrawListProgress(dc.Get(), &rcSeparator, ui::kListProgressSeparator); + } + + // Draw buttons + if (index > -1 && index == hot_item) { + // Draw decrement button + if (button_visible[0]) { + rcButton = button_rect[0]; + dc.FillRect(rcButton, ui::Theme.GetListProgressColor(ui::kListProgressButton)); + rcButton.Inflate(-1, -((button_rect[0].Height() - 1) / 2)); + dc.FillRect(rcButton, ui::Theme.GetListProgressColor(ui::kListProgressBackground)); + } + // Draw increment button + if (button_visible[1]) { + rcButton = button_rect[1]; + dc.FillRect(rcButton, ui::Theme.GetListProgressColor(ui::kListProgressButton)); + rcButton.Inflate(-1, -((button_rect[1].Height() - 1) / 2)); + dc.FillRect(rcButton, ui::Theme.GetListProgressColor(ui::kListProgressBackground)); + rcButton = button_rect[1]; + rcButton.Inflate(-((button_rect[1].Width() - 1) / 2), -1); + dc.FillRect(rcButton, ui::Theme.GetListProgressColor(ui::kListProgressBackground)); + } + } + + // Draw text + std::wstring text; + win::Rect rcText = *rc; + COLORREF text_color = dc.GetTextColor(); + dc.SetBkMode(TRANSPARENT); + + // Separator + rcText.left = rcBar.right; + dc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); + dc.DrawText(L"/", 1, rcText, + DT_CENTER | DT_VCENTER | DT_SINGLELINE); + dc.SetTextColor(text_color); + + // Episodes watched + text = anime::TranslateNumber(eps_watched, L"0"); + rcText.right -= (rcText.Width() / 2) + 4; + if (eps_watched < 1) { + dc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); + } else if (eps_watched > eps_total && eps_total) { + dc.SetTextColor(::GetSysColor(COLOR_HIGHLIGHT)); + } else if (eps_watched < eps_total && anime_item.GetMyStatus() == anime::kCompleted) { + dc.SetTextColor(::GetSysColor(COLOR_HIGHLIGHT)); + } + dc.DrawText(text.c_str(), text.length(), rcText, + DT_RIGHT | DT_VCENTER | DT_SINGLELINE); + dc.SetTextColor(text_color); + + // Total episodes + text = anime::TranslateNumber(eps_total, L"?"); + rcText.left = rcText.right + 8; + rcText.right = rc->right; + if (eps_total < 1) + dc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); + dc.DrawText(text.c_str(), text.length(), rcText, + DT_LEFT | DT_VCENTER | DT_SINGLELINE); + dc.SetTextColor(text_color); + + // Don't destroy the DC + dc.DetachDc(); +} + +void AnimeListDialog::ListView::DrawScoreBox(HDC hdc, RECT* rc, int index, + UINT uItemState, anime::Item& anime_item) { + win::Dc dc = hdc; + win::Rect rcBox = button_rect[2]; + + if (index > -1 && index == hot_item) { + rcBox.right -= 2; + ui::Theme.DrawListProgress(dc.Get(), &rcBox, ui::kListProgressBorder); + rcBox.Inflate(-1, -1); + ui::Theme.DrawListProgress(dc.Get(), &rcBox, ui::kListProgressBackground); + rcBox.Inflate(-4, 0); + + COLORREF text_color = dc.GetTextColor(); + dc.SetBkMode(TRANSPARENT); + + std::wstring text = anime::TranslateNumber(anime_item.GetMyScore()); + dc.SetTextColor(::GetSysColor(COLOR_WINDOWTEXT)); + dc.DrawText(text.c_str(), text.length(), rcBox, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + + dc.EditFont(nullptr, 5); + dc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); + dc.DrawText(L"\u25BC", 1, rcBox, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); + dc.SetTextColor(text_color); + } + + dc.DetachDc(); +} + +LRESULT AnimeListDialog::OnListCustomDraw(LPARAM lParam) { + LPNMLVCUSTOMDRAW pCD = reinterpret_cast(lParam); + + switch (pCD->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + return CDRF_NOTIFYITEMDRAW; + case CDDS_ITEMPREPAINT: + return CDRF_NOTIFYSUBITEMDRAW; + case CDDS_PREERASE: + case CDDS_ITEMPREERASE: + return CDRF_NOTIFYPOSTERASE; + + case CDDS_ITEMPREPAINT | CDDS_SUBITEM: { + auto anime_item = AnimeDatabase.FindItem(static_cast(pCD->nmcd.lItemlParam)); + // Alternate background color + if ((pCD->nmcd.dwItemSpec % 2) && !listview.IsGroupViewEnabled()) + pCD->clrTextBk = ChangeColorBrightness(GetSysColor(COLOR_WINDOW), -0.03f); + // Change text color + if (!anime_item) + return CDRF_NOTIFYPOSTPAINT; + pCD->clrText = GetSysColor(COLOR_WINDOWTEXT); + switch (pCD->iSubItem) { + case 0: + if (anime_item->IsNewEpisodeAvailable() && + Settings.GetBool(taiga::kApp_List_HighlightNewEpisodes)) + pCD->clrText = GetSysColor(COLOR_HIGHLIGHT); + break; + case 2: + if (!anime_item->GetMyScore()) + pCD->clrText = GetSysColor(COLOR_GRAYTEXT); + break; + } + // Indicate currently playing + if (anime_item->GetPlaying()) { + pCD->clrTextBk = ui::kColorLightGreen; + static HFONT hFontDefault = ChangeDCFont(pCD->nmcd.hdc, nullptr, -1, true, -1, -1); + static HFONT hFontBold = reinterpret_cast(GetCurrentObject(pCD->nmcd.hdc, OBJ_FONT)); + SelectObject(pCD->nmcd.hdc, pCD->iSubItem == 0 ? hFontBold : hFontDefault); + return CDRF_NEWFONT | CDRF_NOTIFYPOSTPAINT; + } + return CDRF_NOTIFYPOSTPAINT; + } + + case CDDS_ITEMPOSTPAINT | CDDS_SUBITEM: { + auto anime_item = AnimeDatabase.FindItem(static_cast(pCD->nmcd.lItemlParam)); + if (!anime_item) + return CDRF_DODEFAULT; + if (pCD->iSubItem == 1 || pCD->iSubItem == 2) { + win::Rect rcItem; + listview.GetSubItemRect(pCD->nmcd.dwItemSpec, pCD->iSubItem, &rcItem); + if (!rcItem.IsEmpty()) { + if (pCD->iSubItem == 1) { + listview.DrawProgressBar(pCD->nmcd.hdc, &rcItem, pCD->nmcd.dwItemSpec, + pCD->nmcd.uItemState, *anime_item); + } else if (pCD->iSubItem == 2) { + listview.DrawScoreBox(pCD->nmcd.hdc, &rcItem, pCD->nmcd.dwItemSpec, + pCD->nmcd.uItemState, *anime_item); + } + } + } + return CDRF_DODEFAULT; + } + + default: { + return CDRF_DODEFAULT; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +/* Tab control */ + +LRESULT AnimeListDialog::OnTabNotify(LPARAM lParam) { + switch (reinterpret_cast(lParam)->code) { + // Tab select + case TCN_SELCHANGE: { + int tab_index = tab.GetCurrentlySelected(); + int index = static_cast(tab.GetItemParam(tab_index)); + RefreshList(index); + break; + } + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// + +int AnimeListDialog::GetCurrentId() { + if (current_id_ > anime::ID_UNKNOWN) + if (!AnimeDatabase.FindItem(current_id_)) + current_id_ = anime::ID_UNKNOWN; + + return current_id_; +} + +anime::Item* AnimeListDialog::GetCurrentItem() { + anime::Item* item = nullptr; + + if (current_id_ > anime::ID_UNKNOWN) { + item = AnimeDatabase.FindItem(current_id_); + if (!item) + current_id_ = anime::ID_UNKNOWN; + } + + return item; +} + +void AnimeListDialog::SetCurrentId(int anime_id) { + if (anime_id > anime::ID_UNKNOWN) + if (!AnimeDatabase.FindItem(anime_id)) + anime_id = anime::ID_UNKNOWN; + + current_id_ = anime_id; +} + +int AnimeListDialog::GetListIndex(int anime_id) { + if (IsWindow()) + for (int i = 0; i < listview.GetItemCount(); i++) + if (static_cast(listview.GetItemParam(i)) == anime_id) + return i; + + return -1; +} + +void AnimeListDialog::RefreshList(int index) { + if (!IsWindow()) + return; + + // Remember current status + if (index > anime::kNotInList) + current_status_ = index; + + // Hide list to avoid visual defects and gain performance + listview.Hide(); + listview.DeleteAllItems(); + listview.RefreshItem(-1); + + // Enable group view + bool group_view = !DlgMain.search_bar.filters.text.empty() && + win::GetVersion() > win::kVersionXp; + listview.EnableGroupView(group_view); + + // Add items to list + std::vector group_count(anime::kMyStatusLast); + int group_index = -1; + int icon_index = 0; + int i = 0; + foreach_(it, AnimeDatabase.items) { + anime::Item& anime_item = it->second; + + if (!anime_item.IsInList()) + continue; + if (!group_view) + if (current_status_ != anime_item.GetMyStatus()) + if (current_status_ != anime::kWatching || !it->second.GetMyRewatching()) + continue; + if (!DlgMain.search_bar.filters.CheckItem(anime_item)) + continue; + + group_count.at(anime_item.GetMyStatus())++; + group_index = group_view ? anime_item.GetMyStatus() : -1; + icon_index = anime_item.GetPlaying() ? ui::kIcon16_Play : StatusToIcon(anime_item.GetAiringStatus()); + i = listview.GetItemCount(); + + listview.InsertItem(i, group_index, icon_index, + 0, nullptr, LPSTR_TEXTCALLBACK, + static_cast(anime_item.GetId())); + listview.SetItem(i, 2, anime::TranslateNumber(anime_item.GetMyScore()).c_str()); + listview.SetItem(i, 3, anime::TranslateType(anime_item.GetType()).c_str()); + listview.SetItem(i, 4, anime::TranslateDateToSeasonString(anime_item.GetDateStart()).c_str()); + } + + // Set group headers + if (group_view) { + for (int i = anime::kMyStatusFirst; i < anime::kMyStatusLast; i++) { + std::wstring text = anime::TranslateMyStatus(i, false); + text += group_count.at(i) > 0 ? L" (" + ToWstr(group_count.at(i)) + L")" : L""; + listview.SetGroupText(i, text.c_str()); + } + } + + // Sort items + listview.Sort(listview.GetSortColumn(), + listview.GetSortOrder(), + listview.GetSortType(listview.GetSortColumn()), + ui::ListViewCompareProc); + + // Show again + listview.Show(SW_SHOW); +} + +void AnimeListDialog::RefreshListItem(int anime_id) { + int index = GetListIndex(anime_id); + + if (index > -1) { + auto anime_item = AnimeDatabase.FindItem(anime_id); + int icon_index = anime_item->GetPlaying() ? + ui::kIcon16_Play : StatusToIcon(anime_item->GetAiringStatus()); + listview.SetItemIcon(index, icon_index); + listview.SetItem(index, 2, anime::TranslateNumber(anime_item->GetMyScore()).c_str()); + listview.SetItem(index, 3, anime::TranslateType(anime_item->GetType()).c_str()); + listview.SetItem(index, 4, anime::TranslateDateToSeasonString(anime_item->GetDateStart()).c_str()); + listview.RedrawItems(index, index, true); + } +} + +void AnimeListDialog::RefreshTabs(int index) { + if (!IsWindow()) + return; + + // Remember last index + if (index > anime::kNotInList) + current_status_ = index; + + // Hide + tab.Hide(); + + // Refresh text + for (int i = anime::kMyStatusFirst; i < anime::kMyStatusLast; i++) + tab.SetItemText(i - 1, anime::TranslateMyStatus(i, true).c_str()); + + // Select related tab + bool group_view = !DlgMain.search_bar.filters.text.empty(); + int tab_index = current_status_; + if (group_view) { + tab_index = -1; + } else { + tab_index--; + } + tab.SetCurrentlySelected(tab_index); + + // Show again + tab.Show(SW_SHOW); +} + +void AnimeListDialog::GoToPreviousTab() { + int tab_index = tab.GetCurrentlySelected(); + int tab_count = tab.GetItemCount(); + + if (tab_index > 0) { + --tab_index; + } else { + tab_index = tab_count - 1; + } + + tab.SetCurrentlySelected(tab_index); + + int status = static_cast(tab.GetItemParam(tab_index)); + RefreshList(status); +} + +void AnimeListDialog::GoToNextTab() { + int tab_index = tab.GetCurrentlySelected(); + int tab_count = tab.GetItemCount(); + + if (tab_index < tab_count - 1) { + ++tab_index; + } else { + tab_index = 0; + } + + tab.SetCurrentlySelected(tab_index); + + int status = static_cast(tab.GetItemParam(tab_index)); + RefreshList(status); +} + +} // namespace ui diff --git a/dlg/dlg_anime_list.h b/src/ui/dlg/dlg_anime_list.h similarity index 72% rename from dlg/dlg_anime_list.h rename to src/ui/dlg/dlg_anime_list.h index 7c0323823..d0439e34a 100644 --- a/dlg/dlg_anime_list.h +++ b/src/ui/dlg/dlg_anime_list.h @@ -1,87 +1,91 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_ANIME_LIST_H -#define DLG_ANIME_LIST_H - -#include "../std.h" -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" -#include "../win32/win_gdi.h" - -namespace anime { -class Item; -} - -// ============================================================================= - -class AnimeListDialog : public win32::Dialog { -public: - AnimeListDialog(); - virtual ~AnimeListDialog() {} - - INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - BOOL OnInitDialog(); - LRESULT OnListNotify(LPARAM lParam); - LRESULT OnListCustomDraw(LPARAM lParam); - LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); - void OnSize(UINT uMsg, UINT nType, SIZE size); - LRESULT OnTabNotify(LPARAM lParam); - - int GetCurrentId(); - anime::Item* GetCurrentItem(); - void SetCurrentId(int anime_id); - int GetListIndex(int anime_id); - void RefreshList(int index = -1); - void RefreshListItem(int anime_id); - void RefreshTabs(int index = -1); - -public: - // List-view control - class ListView : public win32::ListView { - public: - ListView(); - - LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - - void DrawProgressBar(HDC hdc, RECT* rc, int index, UINT uItemState, anime::Item& anime_item); - void DrawScoreBox(HDC hdc, RECT* rc, int index, UINT uItemState, anime::Item& anime_item); - - int GetSortType(int column); - void RefreshItem(int index); - - win32::Rect button_rect[3]; - bool button_visible[3]; - bool dragging; - win32::ImageList drag_image; - int hot_item; - win32::Tooltip tooltips; - AnimeListDialog* parent; - } listview; - - // Other controls - win32::Tab tab; - -private: - int current_id_; - int current_status_; -}; - -extern class AnimeListDialog AnimeListDialog; - -#endif // DLG_ANIME_LIST_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_ANIME_LIST_H +#define TAIGA_UI_DLG_ANIME_LIST_H + +#include "win/ctrl/win_ctrl.h" +#include "win/win_dialog.h" +#include "win/win_gdi.h" + +namespace anime { +class Item; +} + +namespace ui { + +class AnimeListDialog : public win::Dialog { +public: + AnimeListDialog(); + virtual ~AnimeListDialog() {} + + INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + BOOL OnInitDialog(); + LRESULT OnListNotify(LPARAM lParam); + LRESULT OnListCustomDraw(LPARAM lParam); + LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); + void OnSize(UINT uMsg, UINT nType, SIZE size); + LRESULT OnTabNotify(LPARAM lParam); + + int GetCurrentId(); + anime::Item* GetCurrentItem(); + void SetCurrentId(int anime_id); + int GetListIndex(int anime_id); + void RefreshList(int index = -1); + void RefreshListItem(int anime_id); + void RefreshTabs(int index = -1); + + void GoToPreviousTab(); + void GoToNextTab(); + +public: + // List-view control + class ListView : public win::ListView { + public: + ListView(); + + LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + + void DrawProgressBar(HDC hdc, RECT* rc, int index, UINT uItemState, anime::Item& anime_item); + void DrawScoreBox(HDC hdc, RECT* rc, int index, UINT uItemState, anime::Item& anime_item); + + int GetSortType(int column); + void RefreshItem(int index); + + win::Rect button_rect[3]; + bool button_visible[3]; + bool dragging; + win::ImageList drag_image; + int hot_item; + win::Tooltip tooltips; + AnimeListDialog* parent; + } listview; + + // Other controls + win::Tab tab; + +private: + int current_id_; + int current_status_; +}; + +extern AnimeListDialog DlgAnimeList; + +} // namespace ui + +#endif // TAIGA_UI_DLG_ANIME_LIST_H \ No newline at end of file diff --git a/src/ui/dlg/dlg_feed_condition.cpp b/src/ui/dlg/dlg_feed_condition.cpp new file mode 100644 index 000000000..a28c54910 --- /dev/null +++ b/src/ui/dlg/dlg_feed_condition.cpp @@ -0,0 +1,346 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include + +#include "base/foreach.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "taiga/resource.h" +#include "ui/dlg/dlg_feed_condition.h" +#include "win/win_gdi.h" + +namespace ui { + +FeedConditionDialog DlgFeedCondition; + +FeedConditionDialog::FeedConditionDialog() { + RegisterDlgClass(L"TaigaFeedConditionW"); +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL FeedConditionDialog::OnInitDialog() { + // Set title + if (condition.element != 0 || + condition.op != 0 || + !condition.value.empty()) + SetText(L"Edit Condition"); + + // Initialize + element_combo_.Attach(GetDlgItem(IDC_COMBO_FEED_ELEMENT)); + operator_combo_.Attach(GetDlgItem(IDC_COMBO_FEED_OPERATOR)); + value_combo_.Attach(GetDlgItem(IDC_COMBO_FEED_VALUE)); + + // Add elements + for (int i = 0; i < kFeedFilterElement_Count; i++) + element_combo_.AddItem(Aggregator.filter_manager.TranslateElement(i).c_str(), i); + + // Set element + element_combo_.SetCurSel(condition.element); + ChooseElement(condition.element); + // Set operator + operator_combo_.SetCurSel(operator_combo_.FindItemData(condition.op)); + // Set value + switch (condition.element) { + case kFeedFilterElement_Meta_Id: { + value_combo_.SetCurSel(0); + for (int i = 0; i < value_combo_.GetCount(); i++) { + int anime_id = static_cast(value_combo_.GetItemData(i)); + if (anime_id == ToInt(condition.value)) { + value_combo_.SetCurSel(i); + break; + } + } + break; + } + case kFeedFilterElement_User_Status: { + int value = ToInt(condition.value); + value_combo_.SetCurSel(value); + break; + } + case kFeedFilterElement_Meta_Status: + case kFeedFilterElement_Meta_Type: + value_combo_.SetCurSel(ToInt(condition.value) - 1); + break; + case kFeedFilterElement_Local_EpisodeAvailable: + value_combo_.SetCurSel(condition.value == L"True" ? 1 : 0); + break; + default: + value_combo_.SetText(condition.value); + } + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// + +INT_PTR FeedConditionDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_CTLCOLORSTATIC: { + win::Dc dc = reinterpret_cast(wParam); + dc.SetBkMode(TRANSPARENT); + dc.DetachDc(); + return reinterpret_cast(::GetSysColorBrush(COLOR_WINDOW)); + } + } + + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + +void FeedConditionDialog::OnCancel() { + // We will check against this in FeedFilterDialog later on + condition.element = kFeedFilterElement_None; + + EndDialog(IDCANCEL); +} + +void FeedConditionDialog::OnOK() { + // Set values + condition.element = static_cast(element_combo_.GetCurSel()); + condition.op = static_cast( + operator_combo_.GetItemData(operator_combo_.GetCurSel())); + switch (condition.element) { + case kFeedFilterElement_Meta_Id: + case kFeedFilterElement_Meta_Status: + case kFeedFilterElement_Meta_Type: + case kFeedFilterElement_User_Status: + condition.value = ToWstr(value_combo_.GetItemData(value_combo_.GetCurSel())); + break; + default: + value_combo_.GetText(condition.value); + } + + EndDialog(IDOK); +} + +BOOL FeedConditionDialog::OnCommand(WPARAM wParam, LPARAM lParam) { + switch (HIWORD(wParam)) { + case CBN_SELCHANGE: { + if (LOWORD(wParam) == IDC_COMBO_FEED_ELEMENT) { + ChooseElement(element_combo_.GetCurSel()); + } + break; + } + } + + return FALSE; +} + +void FeedConditionDialog::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { + win::Dc dc = hdc; + win::Rect rect; + + // Paint background + GetClientRect(&rect); + dc.FillRect(rect, ::GetSysColor(COLOR_WINDOW)); + + // Paint bottom area + win::Rect rect_button; + ::GetClientRect(GetDlgItem(IDCANCEL), &rect_button); + rect.top = rect.bottom - (rect_button.Height() * 2); + dc.FillRect(rect, ::GetSysColor(COLOR_BTNFACE)); + + // Paint line + rect.bottom = rect.top + 1; + dc.FillRect(rect, ::GetSysColor(COLOR_ACTIVEBORDER)); +} + +//////////////////////////////////////////////////////////////////////////////// + +void FeedConditionDialog::ChooseElement(int element_index) { + // Operator + LPARAM op_data = operator_combo_.GetItemData(operator_combo_.GetCurSel()); + operator_combo_.ResetContent(); + + #define ADD_OPERATOR(op) \ + operator_combo_.AddItem(Aggregator.filter_manager.TranslateOperator(op).c_str(), op); + + switch (element_index) { + case kFeedFilterElement_Meta_Id: + case kFeedFilterElement_Episode_Number: + case kFeedFilterElement_Meta_DateStart: + case kFeedFilterElement_Meta_DateEnd: + case kFeedFilterElement_Meta_Episodes: + ADD_OPERATOR(kFeedFilterOperator_Equals); + ADD_OPERATOR(kFeedFilterOperator_NotEquals); + ADD_OPERATOR(kFeedFilterOperator_IsGreaterThan); + ADD_OPERATOR(kFeedFilterOperator_IsGreaterThanOrEqualTo); + ADD_OPERATOR(kFeedFilterOperator_IsLessThan); + ADD_OPERATOR(kFeedFilterOperator_IsLessThanOrEqualTo); + break; + case kFeedFilterElement_Local_EpisodeAvailable: + case kFeedFilterElement_Meta_Status: + case kFeedFilterElement_Meta_Type: + case kFeedFilterElement_User_Status: + ADD_OPERATOR(kFeedFilterOperator_Equals); + ADD_OPERATOR(kFeedFilterOperator_NotEquals); + break; + case kFeedFilterElement_Episode_Title: + case kFeedFilterElement_Episode_Group: + case kFeedFilterElement_Episode_VideoType: + case kFeedFilterElement_File_Title: + case kFeedFilterElement_File_Category: + case kFeedFilterElement_File_Description: + case kFeedFilterElement_File_Link: + ADD_OPERATOR(kFeedFilterOperator_Equals); + ADD_OPERATOR(kFeedFilterOperator_NotEquals); + ADD_OPERATOR(kFeedFilterOperator_BeginsWith); + ADD_OPERATOR(kFeedFilterOperator_EndsWith); + ADD_OPERATOR(kFeedFilterOperator_Contains); + ADD_OPERATOR(kFeedFilterOperator_NotContains); + break; + default: + for (int i = 0; i < kFeedFilterOperator_Count; i++) { + ADD_OPERATOR(i); + } + } + + #undef ADD_OPERATOR + + int op_index = operator_combo_.FindItemData(op_data); + if (op_index == CB_ERR) + op_index = 0; + operator_combo_.SetCurSel(op_index); + + ////////////////////////////////////////////////////////////////////////////// + // Value + + value_combo_.ResetContent(); + + RECT rect; + value_combo_.GetWindowRect(&rect); + int width = rect.right - rect.left; + int height = rect.bottom - rect.top; + ::ScreenToClient(GetWindowHandle(), reinterpret_cast(&rect)); + + #define RECREATE_COMBO(style) \ + value_combo_.Create(0, WC_COMBOBOX, nullptr, \ + style | CBS_AUTOHSCROLL | WS_CHILD | WS_TABSTOP | WS_VISIBLE | WS_VSCROLL, \ + rect.left, rect.top, width, height * 2, \ + GetWindowHandle(), nullptr, nullptr); + + switch (element_index) { + case kFeedFilterElement_File_Category: + RECREATE_COMBO(CBS_DROPDOWN); + value_combo_.AddString(L"Anime"); + value_combo_.AddString(L"Batch"); + value_combo_.AddString(L"Hentai"); + value_combo_.AddString(L"Non-English"); + value_combo_.AddString(L"Other"); + value_combo_.AddString(L"Raws"); + break; + case kFeedFilterElement_Meta_Id: + case kFeedFilterElement_Episode_Title: { + RECREATE_COMBO((element_index == kFeedFilterElement_Meta_Id ? CBS_DROPDOWNLIST : CBS_DROPDOWN)); + typedef std::pair anime_pair; + std::vector title_list; + for (auto it = AnimeDatabase.items.begin(); it != AnimeDatabase.items.end(); ++it) { + switch (it->second.GetMyStatus()) { + case anime::kNotInList: + case anime::kCompleted: + case anime::kDropped: + continue; + default: + title_list.push_back(std::make_pair( + it->second.GetId(), + AnimeDatabase.FindItem(it->second.GetId())->GetTitle())); + } + } + std::sort(title_list.begin(), title_list.end(), + [](const anime_pair& a1, const anime_pair& a2) { + return CompareStrings(a1.second, a2.second) < 0; + }); + if (element_index == kFeedFilterElement_Meta_Id) + value_combo_.AddString(L"(Unknown)"); + foreach_(it, title_list) + value_combo_.AddItem(it->second.c_str(), it->first); + break; + } + case kFeedFilterElement_Meta_DateStart: + case kFeedFilterElement_Meta_DateEnd: + RECREATE_COMBO(CBS_DROPDOWN); + value_combo_.AddString(static_cast(GetDate()).c_str()); + value_combo_.SetCueBannerText(L"YYYY-MM-DD"); + break; + case kFeedFilterElement_Meta_Status: + RECREATE_COMBO(CBS_DROPDOWNLIST); + value_combo_.AddItem(anime::TranslateStatus(anime::kAiring).c_str(), anime::kAiring); + value_combo_.AddItem(anime::TranslateStatus(anime::kFinishedAiring).c_str(), anime::kFinishedAiring); + value_combo_.AddItem(anime::TranslateStatus(anime::kNotYetAired).c_str(), anime::kNotYetAired); + break; + case kFeedFilterElement_Meta_Type: + RECREATE_COMBO(CBS_DROPDOWNLIST); + value_combo_.AddItem(anime::TranslateType(anime::kTv).c_str(), anime::kTv); + value_combo_.AddItem(anime::TranslateType(anime::kOva).c_str(), anime::kOva); + value_combo_.AddItem(anime::TranslateType(anime::kMovie).c_str(), anime::kMovie); + value_combo_.AddItem(anime::TranslateType(anime::kSpecial).c_str(), anime::kSpecial); + value_combo_.AddItem(anime::TranslateType(anime::kOna).c_str(), anime::kOna); + value_combo_.AddItem(anime::TranslateType(anime::kMusic).c_str(), anime::kMusic); + break; + case kFeedFilterElement_User_Status: + RECREATE_COMBO(CBS_DROPDOWNLIST); + value_combo_.AddItem(anime::TranslateMyStatus(anime::kNotInList, false).c_str(), anime::kNotInList); + value_combo_.AddItem(anime::TranslateMyStatus(anime::kWatching, false).c_str(), anime::kWatching); + value_combo_.AddItem(anime::TranslateMyStatus(anime::kCompleted, false).c_str(), anime::kCompleted); + value_combo_.AddItem(anime::TranslateMyStatus(anime::kOnHold, false).c_str(), anime::kOnHold); + value_combo_.AddItem(anime::TranslateMyStatus(anime::kDropped, false).c_str(), anime::kDropped); + value_combo_.AddItem(anime::TranslateMyStatus(anime::kPlanToWatch, false).c_str(), anime::kPlanToWatch); + break; + case kFeedFilterElement_Episode_Number: + case kFeedFilterElement_Meta_Episodes: + RECREATE_COMBO(CBS_DROPDOWN); + value_combo_.AddString(L"%watched%"); + value_combo_.AddString(L"%total%"); + break; + case kFeedFilterElement_Episode_Version: + RECREATE_COMBO(CBS_DROPDOWN); + value_combo_.AddString(L"2"); + value_combo_.AddString(L"3"); + value_combo_.AddString(L"4"); + value_combo_.AddString(L"0"); + break; + case kFeedFilterElement_Local_EpisodeAvailable: + RECREATE_COMBO(CBS_DROPDOWNLIST); + value_combo_.AddString(L"False"); + value_combo_.AddString(L"True"); + break; + case kFeedFilterElement_Episode_VideoResolution: + RECREATE_COMBO(CBS_DROPDOWN); + value_combo_.AddString(L"1080p"); + value_combo_.AddString(L"720p"); + value_combo_.AddString(L"480p"); + value_combo_.AddString(L"400p"); + break; + case kFeedFilterElement_Episode_VideoType: + RECREATE_COMBO(CBS_DROPDOWN); + value_combo_.AddString(L"h264"); + value_combo_.AddString(L"x264"); + value_combo_.AddString(L"XviD"); + break; + default: + RECREATE_COMBO(CBS_DROPDOWN); + break; + } + + #undef RECREATE_COMBO + value_combo_.SetCurSel(0); +} + +} // namespace ui \ No newline at end of file diff --git a/dlg/dlg_feed_condition.h b/src/ui/dlg/dlg_feed_condition.h similarity index 61% rename from dlg/dlg_feed_condition.h rename to src/ui/dlg/dlg_feed_condition.h index 7d1d51382..5126ab38b 100644 --- a/dlg/dlg_feed_condition.h +++ b/src/ui/dlg/dlg_feed_condition.h @@ -1,53 +1,52 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_FEED_CONDITION_H -#define DLG_FEED_CONDITION_H - -#include "../std.h" -#include "../feed.h" -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" - -// ============================================================================= - -class FeedConditionDialog : public win32::Dialog { -public: - FeedConditionDialog(); - virtual ~FeedConditionDialog(); - - INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - void OnCancel(); - BOOL OnCommand(WPARAM wParam, LPARAM lParam); - BOOL OnInitDialog(); - void OnOK(); - void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); - -public: - void ChooseElement(int index); - -public: - FeedFilterCondition condition; - -private: - win32::ComboBox element_combo_, operator_combo_, value_combo_; -}; - -extern class FeedConditionDialog FeedConditionDialog; - -#endif // DLG_FEED_CONDITION_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_FEED_CONDITION_H +#define TAIGA_UI_DLG_FEED_CONDITION_H + +#include "track/feed.h" +#include "win/ctrl/win_ctrl.h" +#include "win/win_dialog.h" + +namespace ui { + +class FeedConditionDialog : public win::Dialog { +public: + FeedConditionDialog(); + ~FeedConditionDialog() {} + + INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + void OnCancel(); + BOOL OnCommand(WPARAM wParam, LPARAM lParam); + BOOL OnInitDialog(); + void OnOK(); + void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); + + void ChooseElement(int index); + + FeedFilterCondition condition; + +private: + win::ComboBox element_combo_, operator_combo_, value_combo_; +}; + +extern FeedConditionDialog DlgFeedCondition; + +} // namespace ui + +#endif // TAIGA_UI_DLG_FEED_CONDITION_H \ No newline at end of file diff --git a/dlg/dlg_feed_filter.cpp b/src/ui/dlg/dlg_feed_filter.cpp similarity index 67% rename from dlg/dlg_feed_filter.cpp rename to src/ui/dlg/dlg_feed_filter.cpp index cfe1888e5..c6d1b28c9 100644 --- a/dlg/dlg_feed_filter.cpp +++ b/src/ui/dlg/dlg_feed_filter.cpp @@ -1,678 +1,679 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_feed_condition.h" -#include "dlg_feed_filter.h" - -#include "../anime_db.h" -#include "../common.h" -#include "../myanimelist.h" -#include "../resource.h" -#include "../string.h" -#include "../taiga.h" -#include "../theme.h" - -#include "../win32/win_gdi.h" -#include "../win32/win_taskdialog.h" - -class FeedFilterDialog FeedFilterDialog; - -// ============================================================================= - -FeedFilterDialog::FeedFilterDialog() - : current_page_(0), - icon_(nullptr) { - RegisterDlgClass(L"TaigaFeedFilterW"); -} - -FeedFilterDialog::~FeedFilterDialog() { - if (icon_) { - ::DeleteObject(icon_); - icon_ = nullptr; - } -} - -INT_PTR FeedFilterDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - // Set main instruction text color - case WM_CTLCOLORSTATIC: { - HDC hdc = reinterpret_cast(wParam); - HWND hwnd_control = reinterpret_cast(lParam); - if (hwnd_control == GetDlgItem(IDC_STATIC_HEADER)) { - SetBkMode(hdc, TRANSPARENT); - SetTextColor(hdc, theme::COLOR_MAININSTRUCTION); - return reinterpret_cast(::GetSysColorBrush(COLOR_WINDOW)); - } - break; - } - - // Forward mouse wheel messages to lists - case WM_MOUSEWHEEL: { - switch (current_page_) { - case 0: - return page_0_.SendDlgItemMessage( - IDC_LIST_FEED_FILTER_PRESETS, uMsg, wParam, lParam); - case 1: - return page_1_.SendDlgItemMessage( - IDC_LIST_FEED_FILTER_CONDITIONS, uMsg, wParam, lParam); - case 2: - return page_2_.SendDlgItemMessage( - IDC_LIST_FEED_FILTER_ANIME, uMsg, wParam, lParam); - } - break; - } - } - - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - -void FeedFilterDialog::OnCancel() { - filter.Reset(); - EndDialog(IDCANCEL); -} - -BOOL FeedFilterDialog::OnInitDialog() { - // Set icon_ - if (!icon_) { - icon_ = UI.ImgList16.GetIcon(ICON16_FUNNEL); - } - //SetIconSmall(icon_); - - // Set main instruction font - main_instructions_label_.Attach(GetDlgItem(IDC_STATIC_HEADER)); - main_instructions_label_.SendMessage(WM_SETFONT, - reinterpret_cast(UI.font_header.Get()), FALSE); - - // Calculate page area - win32::Rect page_area; main_instructions_label_.GetWindowRect(&page_area); - MapWindowPoints(nullptr, m_hWindow, reinterpret_cast(&page_area), 2); - page_area.top += page_area.bottom; - page_area.left *= 2; - - // Create pages - page_0_.Create(IDD_FEED_FILTER_PAGE1, this, page_area); - page_1_.Create(IDD_FEED_FILTER_PAGE2, this, page_area); - page_2_.Create(IDD_FEED_FILTER_PAGE3, this, page_area); - - // Choose and display a page - if (!filter.conditions.empty()) { - SetText(L"Edit Filter"); - current_page_ = 1; - ChoosePage(1); - } else { - ChoosePage(0); - } - - return TRUE; -} - -void FeedFilterDialog::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { - win32::Dc dc = hdc; - win32::Rect rect; - - // Paint background - GetClientRect(&rect); - dc.FillRect(rect, ::GetSysColor(COLOR_WINDOW)); - - // Paint bottom area - win32::Rect rect_button; - ::GetClientRect(GetDlgItem(IDCANCEL), &rect_button); - rect.top = rect.bottom - (rect_button.Height() * 2); - dc.FillRect(rect, ::GetSysColor(COLOR_BTNFACE)); - - // Paint line - rect.bottom = rect.top + 1; - dc.FillRect(rect, ::GetSysColor(COLOR_ACTIVEBORDER)); -} - -BOOL FeedFilterDialog::OnCommand(WPARAM wParam, LPARAM lParam) { - switch (LOWORD(wParam)) { - // Back - case IDNO: - ChoosePage(current_page_ - 1); - return TRUE; - // Next - case IDYES: - ChoosePage(current_page_ + 1); - return TRUE; - } - - return FALSE; -} - -void FeedFilterDialog::ChoosePage(int index) { - switch (index) { - // Page #0 - case 0: - main_instructions_label_.SetText( - L"Choose one of the preset filters, or create a custom one"); - break; - - // Page #1 - case 1: - if (current_page_ == 0) { - page_0_.BuildFilter(filter); - } - page_1_.name_text.SetText(filter.name); - page_1_.RefreshConditionList(); - page_1_.match_combo.SetCurSel(filter.match); - page_1_.action_combo.SetCurSel(filter.action); - main_instructions_label_.SetText( - L"Change filter options and add conditions"); - break; - - // Page #2 - case 2: - if (current_page_ == 1) { - if (page_1_.condition_list.GetItemCount() == 0) { - win32::TaskDialog dlg(APP_TITLE, TD_ICON_ERROR); - dlg.SetMainInstruction( - L"There must be at least one condition in order to create a filter."); - dlg.SetContent( - L"You may add a condition using the Add New Condition button, " - L"or by choosing a preset from the previous page."); - dlg.AddButton(L"OK", IDOK); - dlg.Show(GetWindowHandle()); - return; - } - page_1_.BuildFilter(filter); - } - main_instructions_label_.SetText( - L"Limit this filter to one or more anime title, or leave it blank to apply to all items"); - break; - - // Finished - case 3: - if (current_page_ == 2) { - page_2_.BuildFilter(filter); - } - EndDialog(IDOK); - return; - } - - page_0_.Show(index == 0 ? 1 : 0); - page_1_.Show(index == 1 ? 1 : 0); - page_2_.Show(index == 2 ? 1 : 0); - - EnableDlgItem(IDNO, index > 0); - SetDlgItemText(IDYES, index < 2 ? L"Next >" : L"Finish"); - - current_page_ = index; -} - -// ============================================================================= - -/* Page */ - -void FeedFilterDialog::DialogPage::Create(UINT uResourceID, FeedFilterDialog* parent, const RECT& rect) { - this->parent = parent; - win32::Dialog::Create(uResourceID, parent->GetWindowHandle(), false); - SetPosition(nullptr, rect, SWP_NOSIZE); -} - -INT_PTR FeedFilterDialog::DialogPage::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_CTLCOLORDLG: - case WM_CTLCOLORSTATIC: { - win32::Dc dc = reinterpret_cast(wParam); - dc.SetBkMode(TRANSPARENT); - dc.DetachDC(); - return reinterpret_cast(::GetSysColorBrush(COLOR_WINDOW)); - } - } - - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - -// ============================================================================= - -/* Page 0 */ - -BOOL FeedFilterDialog::DialogPage0::OnInitDialog() { - // Initialize presets list - preset_list.Attach(GetDlgItem(IDC_LIST_FEED_FILTER_PRESETS)); - preset_list.SetExtendedStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP); - preset_list.SetImageList(UI.ImgList16.GetHandle(), LVSIL_NORMAL); - preset_list.SetTheme(); - - // Insert list groups - //preset_list.EnableGroupView(true); - preset_list.InsertGroup(0, L"Presets", true, false); - - // Insert list columns - preset_list.InsertColumn(0, 200, 200, 0, L"Title"); - preset_list.InsertColumn(1, 350, 350, 0, L"Details"); - - // Enable tile view - SIZE size = {660, 40}; - RECT rect = {5, 0, 5, 0}; - preset_list.SetTileViewInfo(1, LVTVIF_FIXEDSIZE, &rect, &size); - preset_list.SetView(LV_VIEW_TILE); - - // Insert presets - for (auto it = Aggregator.filter_manager.presets.begin(); - it != Aggregator.filter_manager.presets.end(); ++it) { - if (it->is_default) continue; - int icon_ = ICON16_FUNNEL; - switch (it->filter.action) { - case FEED_FILTER_ACTION_DISCARD: icon_ = ICON16_FUNNEL_CROSS; break; - case FEED_FILTER_ACTION_SELECT: icon_ = ICON16_FUNNEL_TICK; break; - case FEED_FILTER_ACTION_PREFER: icon_ = ICON16_FUNNEL_PLUS; break; - } - if (it->filter.conditions.empty()) icon_ = ICON16_FUNNEL_PENCIL; - preset_list.InsertItem(it - Aggregator.filter_manager.presets.begin(), - 0, icon_, I_COLUMNSCALLBACK, nullptr, LPSTR_TEXTCALLBACK, - reinterpret_cast(&(*it))); - } - preset_list.SetSelectedItem(0); - - return TRUE; -} - -LRESULT FeedFilterDialog::DialogPage0::OnNotify(int idCtrl, LPNMHDR pnmh) { - switch (idCtrl) { - case IDC_LIST_FEED_FILTER_PRESETS: - switch (pnmh->code) { - // Text callback - case LVN_GETDISPINFO: { - NMLVDISPINFO* plvdi = reinterpret_cast(pnmh); - if (plvdi->item.mask & LVIF_COLUMNS) { - plvdi->item.cColumns = 1; - plvdi->item.puColumns[0] = 1; - } - if (plvdi->item.mask & LVIF_TEXT) { - FeedFilterPreset* preset = reinterpret_cast(plvdi->item.lParam); - if (!preset) break; - switch (plvdi->item.iSubItem) { - case 0: // Filter name - plvdi->item.pszText = const_cast(preset->filter.name.c_str()); - break; - case 1: // Preset description - plvdi->item.pszText = const_cast(preset->description.c_str()); - break; - } - } - return TRUE; - } - - // Double click - case NM_DBLCLK: { - if (preset_list.GetSelectedCount() > 0) { - parent->ChoosePage(1); - } - return TRUE; - } - } - break; - } - - return 0; -} - -bool FeedFilterDialog::DialogPage0::BuildFilter(FeedFilter& filter) { - int selected_item = preset_list.GetNextItem(-1, LVIS_SELECTED); - - if (selected_item > -1) { - // Build filter from preset - FeedFilterPreset* preset = - reinterpret_cast(preset_list.GetItemParam(selected_item)); - if (!preset || preset->filter.conditions.empty()) { - parent->filter.Reset(); - return false; - } - parent->filter = preset->filter; - // Clear selection - ListView_SetItemState(preset_list.GetWindowHandle(), selected_item, 0, LVIS_SELECTED); - } - - return true; -} - -// ============================================================================= - -/* Page #1 */ - -BOOL FeedFilterDialog::DialogPage1::OnInitDialog() { - // Set new font for headers - for (int i = 0; i < 3; i++) { - SendDlgItemMessage(IDC_STATIC_HEADER1 + i, WM_SETFONT, - reinterpret_cast(UI.font_bold.Get()), FALSE); - } - - // Initialize name - name_text.Attach(GetDlgItem(IDC_EDIT_FEED_NAME)); - name_text.SetCueBannerText(L"Type something to identify this filter"); - - // Initialize condition list - condition_list.Attach(GetDlgItem(IDC_LIST_FEED_FILTER_CONDITIONS)); - condition_list.SetExtendedStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP); - condition_list.SetImageList(UI.ImgList16.GetHandle()); - condition_list.SetTheme(); - // Insert list columns - condition_list.InsertColumn(0, 170, 170, 0, L"Element"); - condition_list.InsertColumn(1, 170, 170, 0, L"Operator"); - condition_list.InsertColumn(2, 170, 170, 0, L"Value"); - condition_list.SetColumnWidth(2, LVSCW_AUTOSIZE_USEHEADER); - - // Initialize toolbar - condition_toolbar.Attach(GetDlgItem(IDC_TOOLBAR_FEED_FILTER)); - condition_toolbar.SetImageList(UI.ImgList16.GetHandle(), 16, 16); - condition_toolbar.SendMessage(TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_MIXEDBUTTONS); - // Add toolbar items - BYTE fsState1 = TBSTATE_ENABLED | TBSTATE_WRAP; - BYTE fsState2 = TBSTATE_INDETERMINATE | TBSTATE_WRAP; - condition_toolbar.InsertButton(0, ICON16_PLUS, 100, fsState1, 0, 0, nullptr, L"Add new condition..."); - condition_toolbar.InsertButton(1, ICON16_MINUS, 101, fsState2, 0, 1, nullptr, L"Delete condition"); - condition_toolbar.InsertButton(2, 0, 0, TBSTATE_WRAP, BTNS_SEP, 0, nullptr, nullptr); - condition_toolbar.InsertButton(3, ICON16_ARROW_UP, 103, fsState2, 0, 3, nullptr, L"Move up"); - condition_toolbar.InsertButton(4, ICON16_ARROW_DOWN, 104, fsState2, 0, 4, nullptr, L"Move down"); - - // Initialize options - match_combo.Attach(GetDlgItem(IDC_COMBO_FEED_FILTER_MATCH)); - match_combo.AddString(Aggregator.filter_manager.TranslateMatching(FEED_FILTER_MATCH_ALL).c_str()); - match_combo.AddString(Aggregator.filter_manager.TranslateMatching(FEED_FILTER_MATCH_ANY).c_str()); - action_combo.Attach(GetDlgItem(IDC_COMBO_FEED_FILTER_ACTION)); - action_combo.AddString(Aggregator.filter_manager.TranslateAction(FEED_FILTER_ACTION_DISCARD).c_str()); - action_combo.AddString(Aggregator.filter_manager.TranslateAction(FEED_FILTER_ACTION_SELECT).c_str()); - action_combo.AddString(Aggregator.filter_manager.TranslateAction(FEED_FILTER_ACTION_PREFER).c_str()); - option_combo.Attach(GetDlgItem(IDC_COMBO_FEED_FILTER_OPTION)); - option_combo.AddString(Aggregator.filter_manager.TranslateOption(FEED_FILTER_OPTION_DEFAULT).c_str()); - option_combo.AddString(Aggregator.filter_manager.TranslateOption(FEED_FILTER_OPTION_DEACTIVATE).c_str()); - option_combo.AddString(Aggregator.filter_manager.TranslateOption(FEED_FILTER_OPTION_HIDE).c_str()); - - // Display current filter - name_text.SetText(parent->filter.name); - RefreshConditionList(); - match_combo.SetCurSel(parent->filter.match); - action_combo.SetCurSel(parent->filter.action); - option_combo.SetCurSel(parent->filter.option); - ChangeAction(); - - return TRUE; -} - -BOOL FeedFilterDialog::DialogPage1::OnCommand(WPARAM wParam, LPARAM lParam) { - switch (LOWORD(wParam)) { - // Add new condition - case 100: { - FeedConditionDialog.condition.Reset(); - FeedConditionDialog.Create(IDD_FEED_CONDITION, GetWindowHandle()); - if (FeedConditionDialog.condition.element > -1) { - parent->filter.AddCondition( - FeedConditionDialog.condition.element, - FeedConditionDialog.condition.op, - FeedConditionDialog.condition.value); - RefreshConditionList(); - condition_list.SetSelectedItem(condition_list.GetItemCount() - 1); - } - return TRUE; - } - // Delete condition - case 101: { - int index = condition_list.GetNextItem(-1, LVNI_SELECTED); - if (index > -1) { - condition_list.DeleteItem(index); - parent->filter.conditions.erase(parent->filter.conditions.begin() + index); - } - return TRUE; - } - // Move condition up - case 103: { - int index = condition_list.GetNextItem(-1, LVNI_SELECTED); - if (index > 0) { - iter_swap(parent->filter.conditions.begin() + index, - parent->filter.conditions.begin() + index - 1); - RefreshConditionList(); - condition_list.SetSelectedItem(index - 1); - } - return TRUE; - } - // Move condition down - case 104: { - int index = condition_list.GetNextItem(-1, LVNI_SELECTED); - if (index > -1 && index < condition_list.GetItemCount() - 1) { - iter_swap(parent->filter.conditions.begin() + index, - parent->filter.conditions.begin() + index + 1); - RefreshConditionList(); - condition_list.SetSelectedItem(index + 1); - } - return TRUE; - } - - // Change action - case IDC_COMBO_FEED_FILTER_ACTION: { - if (HIWORD(wParam) == CBN_SELENDOK) { - ChangeAction(); - return TRUE; - } - break; - } - } - - return FALSE; -} - -LRESULT FeedFilterDialog::DialogPage1::OnNotify(int idCtrl, LPNMHDR pnmh) { - switch (idCtrl) { - // Condition list - case IDC_LIST_FEED_FILTER_CONDITIONS: - switch (pnmh->code) { - // List item change - case LVN_ITEMCHANGED: { - int index = condition_list.GetNextItem(-1, LVNI_SELECTED); - int count = condition_list.GetItemCount(); - condition_toolbar.EnableButton(101, index > -1); - condition_toolbar.EnableButton(103, index > 0); - condition_toolbar.EnableButton(104, index > -1 && index < count - 1); - break; - } - // Key press - case LVN_KEYDOWN: { - LPNMLVKEYDOWN pnkd = reinterpret_cast(pnmh); - switch (pnkd->wVKey) { - // Delete condition - case VK_DELETE: - OnCommand(MAKEWPARAM(101, 0), 0); - break; - // Move condition up - case VK_SUBTRACT: - OnCommand(MAKEWPARAM(103, 0), 0); - break; - // Move condition down - case VK_ADD: - OnCommand(MAKEWPARAM(104, 0), 0); - break; - } - break; - } - // Double click - case NM_DBLCLK: { - int selected_item = condition_list.GetNextItem(-1, LVIS_SELECTED); - if (selected_item == -1) break; - FeedFilterCondition* condition = reinterpret_cast( - condition_list.GetItemParam(selected_item)); - if (condition) { - FeedConditionDialog.condition = *condition; - FeedConditionDialog.Create(IDD_FEED_CONDITION, GetWindowHandle()); - if (FeedConditionDialog.condition.element > -1) { - *condition = FeedConditionDialog.condition; - RefreshConditionList(); - condition_list.SetSelectedItem(selected_item); - } - } - break; - } - } - break; - - // Toolbar - case IDC_TOOLBAR_FEED_FILTER: - switch (pnmh->code) { - // Show tooltip text - case TBN_GETINFOTIP: - NMTBGETINFOTIP* git = reinterpret_cast(pnmh); - git->cchTextMax = INFOTIPSIZE; - git->pszText = (LPWSTR)(condition_toolbar.GetButtonTooltip(git->lParam)); - break; - } - break; - } - - return 0; -} - -bool FeedFilterDialog::DialogPage1::BuildFilter(FeedFilter& filter) { - name_text.GetText(filter.name); - filter.match = match_combo.GetCurSel(); - filter.action = action_combo.GetCurSel(); - filter.option = option_combo.GetCurSel(); - - return true; -} - -void FeedFilterDialog::DialogPage1::AddConditionToList(const FeedFilterCondition& condition, int index) { - if (index == -1) index = condition_list.GetItemCount(); - condition_list.InsertItem(index, -1, ICON16_FUNNEL, 0, nullptr, - Aggregator.filter_manager.TranslateElement(condition.element).c_str(), - reinterpret_cast(&condition)); - condition_list.SetItem(index, 1, Aggregator.filter_manager.TranslateOperator(condition.op).c_str()); - condition_list.SetItem(index, 2, Aggregator.filter_manager.TranslateValue(condition).c_str()); -} - -void FeedFilterDialog::DialogPage1::RefreshConditionList() { - condition_list.DeleteAllItems(); - for (auto it = parent->filter.conditions.begin(); it != parent->filter.conditions.end(); ++it) { - AddConditionToList(*it); - } -} - -void FeedFilterDialog::DialogPage1::ChangeAction() { - bool enabled = action_combo.GetCurSel() == FEED_FILTER_ACTION_DISCARD; - - if (!enabled) - option_combo.SetCurSel(FEED_FILTER_OPTION_DEFAULT); - option_combo.Enable(enabled); - option_combo.Show(enabled); - - win32::Window label = GetDlgItem(IDC_STATIC_FEED_FILTER_DISCARDTYPE); - label.Show(enabled); - label.SetWindowHandle(nullptr); -} - -// ============================================================================= - -/* Page 2 */ - -BOOL FeedFilterDialog::DialogPage2::OnInitDialog() { - // Initialize anime list - anime_list.Attach(GetDlgItem(IDC_LIST_FEED_FILTER_ANIME)); - anime_list.EnableGroupView(win32::GetWinVersion() > win32::VERSION_XP); - anime_list.SetExtendedStyle(LVS_EX_CHECKBOXES | LVS_EX_DOUBLEBUFFER); - anime_list.SetImageList(UI.ImgList16.GetHandle()); - anime_list.SetTheme(); - - // Insert list columns - anime_list.InsertColumn(0, 0, 0, 0, L"Title"); - - // Insert list groups - for (int i = mal::MYSTATUS_WATCHING; i <= mal::MYSTATUS_PLANTOWATCH; i++) { - if (i != mal::MYSTATUS_UNKNOWN) { - anime_list.InsertGroup(i, mal::TranslateMyStatus(i, false).c_str(), true, i != mal::MYSTATUS_WATCHING); - } - } - - // Add anime to list - int list_index = 0; - for (auto it = AnimeDatabase.items.begin(); it != AnimeDatabase.items.end(); ++it) { - if (!it->second.IsInList()) continue; - anime_list.InsertItem(list_index, it->second.GetMyStatus(), - StatusToIcon(it->second.GetAiringStatus()), 0, nullptr, - LPSTR_TEXTCALLBACK, static_cast(it->second.GetId())); - for (auto id = parent->filter.anime_ids.begin(); id != parent->filter.anime_ids.end(); ++id) { - if (*id == it->second.GetId()) { - anime_list.SetCheckState(list_index, TRUE); - break; - } - } - ++list_index; - } - - // Sort items - anime_list.Sort(0, 1, 0, ListViewCompareProc); - - // Resize header - anime_list.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); - - return TRUE; -} - -LRESULT FeedFilterDialog::DialogPage2::OnNotify(int idCtrl, LPNMHDR pnmh) { - switch (idCtrl) { - // Anime list - case IDC_LIST_FEED_FILTER_ANIME: - switch (pnmh->code) { - // Text callback - case LVN_GETDISPINFO: { - NMLVDISPINFO* plvdi = reinterpret_cast(pnmh); - auto anime_item = AnimeDatabase.FindItem(static_cast(plvdi->item.lParam)); - if (!anime_item) break; - switch (plvdi->item.iSubItem) { - case 0: // Anime title - plvdi->item.pszText = const_cast(anime_item->GetTitle().data()); - break; - } - break; - } - // Check/uncheck - case LVN_ITEMCHANGED: { - LPNMLISTVIEW pnmv = reinterpret_cast(pnmh); - if (pnmv->uOldState != 0 && (pnmv->uNewState == 0x1000 || pnmv->uNewState == 0x2000)) { - wstring text; - for (int i = 0; i < anime_list.GetItemCount(); i++) { - auto anime_item = AnimeDatabase.FindItem(static_cast(anime_list.GetItemParam(i))); - if (anime_item && anime_list.GetCheckState(i)) { - AppendString(text, anime_item->GetTitle()); - } - } - if (text.empty()) text = L"(nothing)"; - text = L"Currently limited to: " + text; - SetDlgItemText(IDC_STATIC_FEED_FILTER_LIMIT, text.c_str()); - } - break; - } - } - break; - } - - return 0; -} - -bool FeedFilterDialog::DialogPage2::BuildFilter(FeedFilter& filter) { - filter.anime_ids.clear(); - - for (int i = 0; i < anime_list.GetItemCount(); i++) { - if (anime_list.GetCheckState(i)) { - auto anime_item = AnimeDatabase.FindItem(static_cast(anime_list.GetItemParam(i))); - if (anime_item) filter.anime_ids.push_back(anime_item->GetId()); - } - } - - return true; -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "taiga/resource.h" +#include "taiga/taiga.h" +#include "ui/dlg/dlg_feed_condition.h" +#include "ui/dlg/dlg_feed_filter.h" +#include "ui/list.h" +#include "ui/theme.h" +#include "ui/ui.h" +#include "win/win_gdi.h" +#include "win/win_taskdialog.h" + +namespace ui { + +FeedFilterDialog DlgFeedFilter; + +FeedFilterDialog::FeedFilterDialog() + : current_page_(0), + icon_(nullptr) { + RegisterDlgClass(L"TaigaFeedFilterW"); +} + +FeedFilterDialog::~FeedFilterDialog() { + if (icon_) { + ::DeleteObject(icon_); + icon_ = nullptr; + } +} + +INT_PTR FeedFilterDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + // Set main instruction text color + case WM_CTLCOLORSTATIC: { + HDC hdc = reinterpret_cast(wParam); + HWND hwnd_control = reinterpret_cast(lParam); + if (hwnd_control == GetDlgItem(IDC_STATIC_HEADER)) { + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, ui::kColorMainInstruction); + return reinterpret_cast(::GetSysColorBrush(COLOR_WINDOW)); + } + break; + } + + // Forward mouse wheel messages to lists + case WM_MOUSEWHEEL: { + switch (current_page_) { + case 0: + return page_0_.SendDlgItemMessage( + IDC_LIST_FEED_FILTER_PRESETS, uMsg, wParam, lParam); + case 1: + return page_1_.SendDlgItemMessage( + IDC_LIST_FEED_FILTER_CONDITIONS, uMsg, wParam, lParam); + case 2: + return page_2_.SendDlgItemMessage( + IDC_LIST_FEED_FILTER_ANIME, uMsg, wParam, lParam); + } + break; + } + } + + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + +void FeedFilterDialog::OnCancel() { + filter.Reset(); + EndDialog(IDCANCEL); +} + +BOOL FeedFilterDialog::OnInitDialog() { + // Set icon_ + if (!icon_) + icon_ = ui::Theme.GetImageList16().GetIcon(ui::kIcon16_Funnel); + //SetIconSmall(icon_); + + // Set main instruction font + main_instructions_label_.Attach(GetDlgItem(IDC_STATIC_HEADER)); + main_instructions_label_.SendMessage(WM_SETFONT, + reinterpret_cast(ui::Theme.GetHeaderFont()), FALSE); + + // Calculate page area + win::Rect page_area; main_instructions_label_.GetWindowRect(&page_area); + MapWindowPoints(nullptr, GetWindowHandle(), reinterpret_cast(&page_area), 2); + page_area.top += page_area.bottom; + page_area.left *= 2; + + // Create pages + page_0_.Create(IDD_FEED_FILTER_PAGE1, this, page_area); + page_1_.Create(IDD_FEED_FILTER_PAGE2, this, page_area); + page_2_.Create(IDD_FEED_FILTER_PAGE3, this, page_area); + + // Choose and display a page + if (!filter.conditions.empty()) { + SetText(L"Edit Filter"); + current_page_ = 1; + ChoosePage(1); + } else { + ChoosePage(0); + } + + return TRUE; +} + +void FeedFilterDialog::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { + win::Dc dc = hdc; + win::Rect rect; + + // Paint background + GetClientRect(&rect); + dc.FillRect(rect, ::GetSysColor(COLOR_WINDOW)); + + // Paint bottom area + win::Rect rect_button; + ::GetClientRect(GetDlgItem(IDCANCEL), &rect_button); + rect.top = rect.bottom - (rect_button.Height() * 2); + dc.FillRect(rect, ::GetSysColor(COLOR_BTNFACE)); + + // Paint line + rect.bottom = rect.top + 1; + dc.FillRect(rect, ::GetSysColor(COLOR_ACTIVEBORDER)); +} + +BOOL FeedFilterDialog::OnCommand(WPARAM wParam, LPARAM lParam) { + switch (LOWORD(wParam)) { + // Back + case IDNO: + ChoosePage(current_page_ - 1); + return TRUE; + // Next + case IDYES: + ChoosePage(current_page_ + 1); + return TRUE; + } + + return FALSE; +} + +void FeedFilterDialog::ChoosePage(int index) { + switch (index) { + // Page #0 + case 0: + main_instructions_label_.SetText( + L"Choose one of the preset filters, or create a custom one"); + break; + + // Page #1 + case 1: + if (current_page_ == 0) + page_0_.BuildFilter(filter); + page_1_.name_text.SetText(filter.name); + page_1_.RefreshConditionList(); + page_1_.match_combo.SetCurSel(filter.match); + page_1_.action_combo.SetCurSel(filter.action); + main_instructions_label_.SetText( + L"Change filter options and add conditions"); + break; + + // Page #2 + case 2: + if (current_page_ == 1) { + if (page_1_.condition_list.GetItemCount() == 0) { + win::TaskDialog dlg(TAIGA_APP_TITLE, TD_ICON_ERROR); + dlg.SetMainInstruction( + L"There must be at least one condition in order to create a filter."); + dlg.SetContent( + L"You may add a condition using the Add New Condition button, " + L"or by choosing a preset from the previous page."); + dlg.AddButton(L"OK", IDOK); + dlg.Show(GetWindowHandle()); + return; + } + page_1_.BuildFilter(filter); + } + main_instructions_label_.SetText( + L"Limit this filter to one or more anime title, " + L"or leave it blank to apply to all items"); + break; + + // Finished + case 3: + if (current_page_ == 2) + page_2_.BuildFilter(filter); + EndDialog(IDOK); + return; + } + + page_0_.Show(index == 0 ? 1 : 0); + page_1_.Show(index == 1 ? 1 : 0); + page_2_.Show(index == 2 ? 1 : 0); + + EnableDlgItem(IDNO, index > 0); + SetDlgItemText(IDYES, index < 2 ? L"Next >" : L"Finish"); + + current_page_ = index; +} + +//////////////////////////////////////////////////////////////////////////////// + +/* Page */ + +void FeedFilterDialog::DialogPage::Create(UINT uResourceID, FeedFilterDialog* parent, const RECT& rect) { + this->parent = parent; + win::Dialog::Create(uResourceID, parent->GetWindowHandle(), false); + SetPosition(nullptr, rect, SWP_NOSIZE); +} + +INT_PTR FeedFilterDialog::DialogPage::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_CTLCOLORDLG: + case WM_CTLCOLORSTATIC: { + win::Dc dc = reinterpret_cast(wParam); + dc.SetBkMode(TRANSPARENT); + dc.DetachDc(); + return reinterpret_cast(::GetSysColorBrush(COLOR_WINDOW)); + } + } + + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + +//////////////////////////////////////////////////////////////////////////////// + +/* Page 0 */ + +BOOL FeedFilterDialog::DialogPage0::OnInitDialog() { + // Initialize presets list + preset_list.Attach(GetDlgItem(IDC_LIST_FEED_FILTER_PRESETS)); + preset_list.SetExtendedStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP); + preset_list.SetImageList(ui::Theme.GetImageList16().GetHandle(), LVSIL_NORMAL); + preset_list.SetTheme(); + + // Insert list groups + //preset_list.EnableGroupView(true); + preset_list.InsertGroup(0, L"Presets", true, false); + + // Insert list columns + preset_list.InsertColumn(0, 200, 200, 0, L"Title"); + preset_list.InsertColumn(1, 350, 350, 0, L"Details"); + + // Enable tile view + SIZE size = {660, 40}; + RECT rect = {5, 0, 5, 0}; + preset_list.SetTileViewInfo(1, LVTVIF_FIXEDSIZE, &rect, &size); + preset_list.SetView(LV_VIEW_TILE); + + // Insert presets + foreach_(it, Aggregator.filter_manager.presets) { + if (it->is_default) + continue; + int icon_ = ui::kIcon16_Funnel; + switch (it->filter.action) { + case kFeedFilterActionDiscard: icon_ = ui::kIcon16_FunnelCross; break; + case kFeedFilterActionSelect: icon_ = ui::kIcon16_FunnelTick; break; + case kFeedFilterActionPrefer: icon_ = ui::kIcon16_FunnelPlus; break; + } + if (it->filter.conditions.empty()) + icon_ = ui::kIcon16_FunnelPencil; + preset_list.InsertItem(it - Aggregator.filter_manager.presets.begin(), + 0, icon_, I_COLUMNSCALLBACK, nullptr, LPSTR_TEXTCALLBACK, + reinterpret_cast(&(*it))); + } + preset_list.SetSelectedItem(0); + + return TRUE; +} + +LRESULT FeedFilterDialog::DialogPage0::OnNotify(int idCtrl, LPNMHDR pnmh) { + switch (idCtrl) { + case IDC_LIST_FEED_FILTER_PRESETS: + switch (pnmh->code) { + // Text callback + case LVN_GETDISPINFO: { + NMLVDISPINFO* plvdi = reinterpret_cast(pnmh); + if (plvdi->item.mask & LVIF_COLUMNS) { + plvdi->item.cColumns = 1; + plvdi->item.puColumns[0] = 1; + } + if (plvdi->item.mask & LVIF_TEXT) { + FeedFilterPreset* preset = reinterpret_cast(plvdi->item.lParam); + if (!preset) break; + switch (plvdi->item.iSubItem) { + case 0: // Filter name + plvdi->item.pszText = const_cast(preset->filter.name.c_str()); + break; + case 1: // Preset description + plvdi->item.pszText = const_cast(preset->description.c_str()); + break; + } + } + return TRUE; + } + + // Double click + case NM_DBLCLK: { + if (preset_list.GetSelectedCount() > 0) + parent->ChoosePage(1); + return TRUE; + } + } + break; + } + + return 0; +} + +bool FeedFilterDialog::DialogPage0::BuildFilter(FeedFilter& filter) { + int selected_item = preset_list.GetNextItem(-1, LVIS_SELECTED); + + if (selected_item > -1) { + // Build filter from preset + auto preset = reinterpret_cast( + preset_list.GetItemParam(selected_item)); + if (!preset || preset->filter.conditions.empty()) { + parent->filter.Reset(); + return false; + } + parent->filter = preset->filter; + // Clear selection + ListView_SetItemState(preset_list.GetWindowHandle(), selected_item, 0, LVIS_SELECTED); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +/* Page #1 */ + +BOOL FeedFilterDialog::DialogPage1::OnInitDialog() { + // Set new font for headers + for (int i = 0; i < 3; i++) + SendDlgItemMessage(IDC_STATIC_HEADER1 + i, WM_SETFONT, + reinterpret_cast(ui::Theme.GetBoldFont()), FALSE); + + // Initialize name + name_text.Attach(GetDlgItem(IDC_EDIT_FEED_NAME)); + name_text.SetCueBannerText(L"Type something to identify this filter"); + + // Initialize condition list + condition_list.Attach(GetDlgItem(IDC_LIST_FEED_FILTER_CONDITIONS)); + condition_list.SetExtendedStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP); + condition_list.SetImageList(ui::Theme.GetImageList16().GetHandle()); + condition_list.SetTheme(); + // Insert list columns + condition_list.InsertColumn(0, 170, 170, 0, L"Element"); + condition_list.InsertColumn(1, 170, 170, 0, L"Operator"); + condition_list.InsertColumn(2, 170, 170, 0, L"Value"); + condition_list.SetColumnWidth(2, LVSCW_AUTOSIZE_USEHEADER); + + // Initialize toolbar + condition_toolbar.Attach(GetDlgItem(IDC_TOOLBAR_FEED_FILTER)); + condition_toolbar.SetImageList(ui::Theme.GetImageList16().GetHandle(), 16, 16); + condition_toolbar.SendMessage(TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_MIXEDBUTTONS); + // Add toolbar items + BYTE fsState1 = TBSTATE_ENABLED | TBSTATE_WRAP; + BYTE fsState2 = TBSTATE_INDETERMINATE | TBSTATE_WRAP; + condition_toolbar.InsertButton(0, ui::kIcon16_Plus, 100, fsState1, 0, 0, nullptr, L"Add new condition..."); + condition_toolbar.InsertButton(1, ui::kIcon16_Minus, 101, fsState2, 0, 1, nullptr, L"Delete condition"); + condition_toolbar.InsertButton(2, 0, 0, TBSTATE_WRAP, BTNS_SEP, 0, nullptr, nullptr); + condition_toolbar.InsertButton(3, ui::kIcon16_ArrowUp, 103, fsState2, 0, 3, nullptr, L"Move up"); + condition_toolbar.InsertButton(4, ui::kIcon16_ArrowDown, 104, fsState2, 0, 4, nullptr, L"Move down"); + + // Initialize options + match_combo.Attach(GetDlgItem(IDC_COMBO_FEED_FILTER_MATCH)); + match_combo.AddString(Aggregator.filter_manager.TranslateMatching(kFeedFilterMatchAll).c_str()); + match_combo.AddString(Aggregator.filter_manager.TranslateMatching(kFeedFilterMatchAny).c_str()); + action_combo.Attach(GetDlgItem(IDC_COMBO_FEED_FILTER_ACTION)); + action_combo.AddString(Aggregator.filter_manager.TranslateAction(kFeedFilterActionDiscard).c_str()); + action_combo.AddString(Aggregator.filter_manager.TranslateAction(kFeedFilterActionSelect).c_str()); + action_combo.AddString(Aggregator.filter_manager.TranslateAction(kFeedFilterActionPrefer).c_str()); + option_combo.Attach(GetDlgItem(IDC_COMBO_FEED_FILTER_OPTION)); + option_combo.AddString(Aggregator.filter_manager.TranslateOption(kFeedFilterOptionDefault).c_str()); + option_combo.AddString(Aggregator.filter_manager.TranslateOption(kFeedFilterOptionDeactivate).c_str()); + option_combo.AddString(Aggregator.filter_manager.TranslateOption(kFeedFilterOptionHide).c_str()); + + // Display current filter + name_text.SetText(parent->filter.name); + RefreshConditionList(); + match_combo.SetCurSel(parent->filter.match); + action_combo.SetCurSel(parent->filter.action); + option_combo.SetCurSel(parent->filter.option); + ChangeAction(); + + return TRUE; +} + +BOOL FeedFilterDialog::DialogPage1::OnCommand(WPARAM wParam, LPARAM lParam) { + switch (LOWORD(wParam)) { + // Add new condition + case 100: { + DlgFeedCondition.condition.Reset(); + DlgFeedCondition.Create(IDD_FEED_CONDITION, GetWindowHandle()); + if (DlgFeedCondition.condition.element != kFeedFilterElement_None) { + parent->filter.AddCondition( + DlgFeedCondition.condition.element, + DlgFeedCondition.condition.op, + DlgFeedCondition.condition.value); + RefreshConditionList(); + condition_list.SetSelectedItem(condition_list.GetItemCount() - 1); + } + return TRUE; + } + // Delete condition + case 101: { + int index = condition_list.GetNextItem(-1, LVNI_SELECTED); + if (index > -1) { + condition_list.DeleteItem(index); + parent->filter.conditions.erase(parent->filter.conditions.begin() + index); + } + return TRUE; + } + // Move condition up + case 103: { + int index = condition_list.GetNextItem(-1, LVNI_SELECTED); + if (index > 0) { + iter_swap(parent->filter.conditions.begin() + index, + parent->filter.conditions.begin() + index - 1); + RefreshConditionList(); + condition_list.SetSelectedItem(index - 1); + } + return TRUE; + } + // Move condition down + case 104: { + int index = condition_list.GetNextItem(-1, LVNI_SELECTED); + if (index > -1 && index < condition_list.GetItemCount() - 1) { + iter_swap(parent->filter.conditions.begin() + index, + parent->filter.conditions.begin() + index + 1); + RefreshConditionList(); + condition_list.SetSelectedItem(index + 1); + } + return TRUE; + } + + // Change action + case IDC_COMBO_FEED_FILTER_ACTION: { + if (HIWORD(wParam) == CBN_SELENDOK) { + ChangeAction(); + return TRUE; + } + break; + } + } + + return FALSE; +} + +LRESULT FeedFilterDialog::DialogPage1::OnNotify(int idCtrl, LPNMHDR pnmh) { + switch (idCtrl) { + // Condition list + case IDC_LIST_FEED_FILTER_CONDITIONS: + switch (pnmh->code) { + // List item change + case LVN_ITEMCHANGED: { + int index = condition_list.GetNextItem(-1, LVNI_SELECTED); + int count = condition_list.GetItemCount(); + condition_toolbar.EnableButton(101, index > -1); + condition_toolbar.EnableButton(103, index > 0); + condition_toolbar.EnableButton(104, index > -1 && index < count - 1); + break; + } + // Key press + case LVN_KEYDOWN: { + LPNMLVKEYDOWN pnkd = reinterpret_cast(pnmh); + switch (pnkd->wVKey) { + // Delete condition + case VK_DELETE: + OnCommand(MAKEWPARAM(101, 0), 0); + break; + // Move condition up + case VK_SUBTRACT: + OnCommand(MAKEWPARAM(103, 0), 0); + break; + // Move condition down + case VK_ADD: + OnCommand(MAKEWPARAM(104, 0), 0); + break; + } + break; + } + // Double click + case NM_DBLCLK: { + int selected_item = condition_list.GetNextItem(-1, LVIS_SELECTED); + if (selected_item == -1) + break; + auto condition = reinterpret_cast( + condition_list.GetItemParam(selected_item)); + if (condition) { + DlgFeedCondition.condition = *condition; + DlgFeedCondition.Create(IDD_FEED_CONDITION, GetWindowHandle()); + if (DlgFeedCondition.condition.element != kFeedFilterElement_None) { + *condition = DlgFeedCondition.condition; + RefreshConditionList(); + condition_list.SetSelectedItem(selected_item); + } + } + break; + } + } + break; + + // Toolbar + case IDC_TOOLBAR_FEED_FILTER: + switch (pnmh->code) { + // Show tooltip text + case TBN_GETINFOTIP: + auto git = reinterpret_cast(pnmh); + git->cchTextMax = INFOTIPSIZE; + git->pszText = (LPWSTR)(condition_toolbar.GetButtonTooltip(git->lParam)); + break; + } + break; + } + + return 0; +} + +bool FeedFilterDialog::DialogPage1::BuildFilter(FeedFilter& filter) { + name_text.GetText(filter.name); + filter.match = static_cast(match_combo.GetCurSel()); + filter.action = static_cast(action_combo.GetCurSel()); + filter.option = static_cast(option_combo.GetCurSel()); + + return true; +} + +void FeedFilterDialog::DialogPage1::AddConditionToList(const FeedFilterCondition& condition, int index) { + if (index == -1) + index = condition_list.GetItemCount(); + + condition_list.InsertItem(index, -1, ui::kIcon16_Funnel, 0, nullptr, + Aggregator.filter_manager.TranslateElement(condition.element).c_str(), + reinterpret_cast(&condition)); + condition_list.SetItem(index, 1, Aggregator.filter_manager.TranslateOperator(condition.op).c_str()); + condition_list.SetItem(index, 2, Aggregator.filter_manager.TranslateValue(condition).c_str()); +} + +void FeedFilterDialog::DialogPage1::RefreshConditionList() { + condition_list.DeleteAllItems(); + + foreach_(it, parent->filter.conditions) + AddConditionToList(*it); +} + +void FeedFilterDialog::DialogPage1::ChangeAction() { + bool enabled = action_combo.GetCurSel() == kFeedFilterActionDiscard; + + if (!enabled) + option_combo.SetCurSel(kFeedFilterOptionDefault); + option_combo.Enable(enabled); + option_combo.Show(enabled); + + win::Window label = GetDlgItem(IDC_STATIC_FEED_FILTER_DISCARDTYPE); + label.Show(enabled); + label.SetWindowHandle(nullptr); +} + +//////////////////////////////////////////////////////////////////////////////// + +/* Page 2 */ + +BOOL FeedFilterDialog::DialogPage2::OnInitDialog() { + // Initialize anime list + anime_list.Attach(GetDlgItem(IDC_LIST_FEED_FILTER_ANIME)); + anime_list.EnableGroupView(win::GetVersion() > win::kVersionXp); + anime_list.SetExtendedStyle(LVS_EX_CHECKBOXES | LVS_EX_DOUBLEBUFFER); + anime_list.SetImageList(ui::Theme.GetImageList16().GetHandle()); + anime_list.SetTheme(); + + // Insert list columns + anime_list.InsertColumn(0, 0, 0, 0, L"Title"); + + // Insert list groups + for (int i = anime::kMyStatusFirst; i < anime::kMyStatusLast; i++) + anime_list.InsertGroup(i, anime::TranslateMyStatus(i, false).c_str(), + true, i != anime::kWatching); + + // Add anime to list + int list_index = 0; + foreach_(it, AnimeDatabase.items) { + if (!it->second.IsInList()) + continue; + anime_list.InsertItem(list_index, it->second.GetMyStatus(), + StatusToIcon(it->second.GetAiringStatus()), 0, nullptr, + LPSTR_TEXTCALLBACK, static_cast(it->second.GetId())); + foreach_(id, parent->filter.anime_ids) { + if (*id == it->second.GetId()) { + anime_list.SetCheckState(list_index, TRUE); + break; + } + } + ++list_index; + } + + // Sort items + anime_list.Sort(0, 1, 0, ui::ListViewCompareProc); + + // Resize header + anime_list.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); + + return TRUE; +} + +LRESULT FeedFilterDialog::DialogPage2::OnNotify(int idCtrl, LPNMHDR pnmh) { + switch (idCtrl) { + // Anime list + case IDC_LIST_FEED_FILTER_ANIME: + switch (pnmh->code) { + // Text callback + case LVN_GETDISPINFO: { + NMLVDISPINFO* plvdi = reinterpret_cast(pnmh); + auto anime_item = AnimeDatabase.FindItem(static_cast(plvdi->item.lParam)); + if (!anime_item) + break; + switch (plvdi->item.iSubItem) { + case 0: // Anime title + plvdi->item.pszText = const_cast(anime_item->GetTitle().data()); + break; + } + break; + } + // Check/uncheck + case LVN_ITEMCHANGED: { + LPNMLISTVIEW pnmv = reinterpret_cast(pnmh); + if (pnmv->uOldState != 0 && (pnmv->uNewState == 0x1000 || pnmv->uNewState == 0x2000)) { + std::wstring text; + for (int i = 0; i < anime_list.GetItemCount(); i++) { + auto anime_item = AnimeDatabase.FindItem(static_cast(anime_list.GetItemParam(i))); + if (anime_item && anime_list.GetCheckState(i)) + AppendString(text, anime_item->GetTitle()); + } + if (text.empty()) + text = L"(nothing)"; + text = L"Currently limited to: " + text; + SetDlgItemText(IDC_STATIC_FEED_FILTER_LIMIT, text.c_str()); + } + break; + } + } + break; + } + + return 0; +} + +bool FeedFilterDialog::DialogPage2::BuildFilter(FeedFilter& filter) { + filter.anime_ids.clear(); + + for (int i = 0; i < anime_list.GetItemCount(); i++) { + if (anime_list.GetCheckState(i)) { + auto anime_item = AnimeDatabase.FindItem(static_cast(anime_list.GetItemParam(i))); + if (anime_item) + filter.anime_ids.push_back(anime_item->GetId()); + } + } + + return true; +} + +} // namespace ui diff --git a/dlg/dlg_feed_filter.h b/src/ui/dlg/dlg_feed_filter.h similarity index 68% rename from dlg/dlg_feed_filter.h rename to src/ui/dlg/dlg_feed_filter.h index e1c48dd9f..21f71a54d 100644 --- a/dlg/dlg_feed_filter.h +++ b/src/ui/dlg/dlg_feed_filter.h @@ -1,100 +1,99 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_FEED_FILTER_H -#define DLG_FEED_FILTER_H - -#include "../std.h" -#include "../feed.h" -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" - -// ============================================================================= - -class FeedFilterDialog : public win32::Dialog { -public: - FeedFilterDialog(); - virtual ~FeedFilterDialog(); - - INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - void OnCancel(); - BOOL OnCommand(WPARAM wParam, LPARAM lParam); - BOOL OnInitDialog(); - void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); - -public: - void ChoosePage(int index); - -public: - FeedFilter filter; - -private: - int current_page_; - HICON icon_; - win32::Window main_instructions_label_; - - // Page - class DialogPage : public win32::Dialog { - public: - void Create(UINT uResourceID, FeedFilterDialog* parent, const RECT& rect); - INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - public: - FeedFilterDialog* parent; - }; - - // Page #0 - class DialogPage0 : public DialogPage { - public: - BOOL OnInitDialog(); - LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); - bool BuildFilter(FeedFilter& filter); - public: - win32::ListView preset_list; - } page_0_; - - // Page #1 - class DialogPage1 : public DialogPage { - public: - BOOL OnCommand(WPARAM wParam, LPARAM lParam); - BOOL OnInitDialog(); - LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); - bool BuildFilter(FeedFilter& filter); - void AddConditionToList(const FeedFilterCondition& condition, int index = -1); - void RefreshConditionList(); - void ChangeAction(); - public: - win32::ComboBox action_combo, match_combo, option_combo; - win32::Edit name_text; - win32::ListView condition_list; - win32::Toolbar condition_toolbar; - } page_1_; - - // Page #2 - class DialogPage2 : public DialogPage { - public: - BOOL OnInitDialog(); - LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); - bool BuildFilter(FeedFilter& filter); - public: - win32::ListView anime_list; - } page_2_; -}; - -extern class FeedFilterDialog FeedFilterDialog; - -#endif // DLG_FEED_FILTER_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_FEED_FILTER_H +#define TAIGA_UI_DLG_FEED_FILTER_H + +#include "track/feed.h" +#include "win/ctrl/win_ctrl.h" +#include "win/win_dialog.h" + +namespace ui { + +class FeedFilterDialog : public win::Dialog { +public: + FeedFilterDialog(); + ~FeedFilterDialog(); + + INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + void OnCancel(); + BOOL OnCommand(WPARAM wParam, LPARAM lParam); + BOOL OnInitDialog(); + void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); + + void ChoosePage(int index); + + FeedFilter filter; + +private: + int current_page_; + HICON icon_; + win::Window main_instructions_label_; + + // Page + class DialogPage : public win::Dialog { + public: + void Create(UINT uResourceID, FeedFilterDialog* parent, const RECT& rect); + INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + public: + FeedFilterDialog* parent; + }; + + // Page #0 + class DialogPage0 : public DialogPage { + public: + BOOL OnInitDialog(); + LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); + bool BuildFilter(FeedFilter& filter); + public: + win::ListView preset_list; + } page_0_; + + // Page #1 + class DialogPage1 : public DialogPage { + public: + BOOL OnCommand(WPARAM wParam, LPARAM lParam); + BOOL OnInitDialog(); + LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); + bool BuildFilter(FeedFilter& filter); + void AddConditionToList(const FeedFilterCondition& condition, int index = -1); + void RefreshConditionList(); + void ChangeAction(); + public: + win::ComboBox action_combo, match_combo, option_combo; + win::Edit name_text; + win::ListView condition_list; + win::Toolbar condition_toolbar; + } page_1_; + + // Page #2 + class DialogPage2 : public DialogPage { + public: + BOOL OnInitDialog(); + LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); + bool BuildFilter(FeedFilter& filter); + public: + win::ListView anime_list; + } page_2_; +}; + +extern FeedFilterDialog DlgFeedFilter; + +} // namespace ui + +#endif // TAIGA_UI_DLG_FEED_FILTER_H diff --git a/dlg/dlg_format.cpp b/src/ui/dlg/dlg_format.cpp similarity index 50% rename from dlg/dlg_format.cpp rename to src/ui/dlg/dlg_format.cpp index 6a90eba64..7784a9baa 100644 --- a/dlg/dlg_format.cpp +++ b/src/ui/dlg/dlg_format.cpp @@ -1,257 +1,227 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_format.h" - -#include "../anime_db.h" -#include "../common.h" -#include "../myanimelist.h" -#include "../recognition.h" -#include "../resource.h" -#include "../settings.h" -#include "../string.h" -#include "../theme.h" - -anime::Episode PreviewEpisode; -anime::Item PreviewAnime; -class FormatDialog FormatDialog; -vector functions, keywords; - -// ============================================================================= - -FormatDialog::FormatDialog() : - mode(FORMAT_MODE_HTTP) -{ - RegisterDlgClass(L"TaigaFormatW"); -} - -BOOL FormatDialog::OnInitDialog() { - // Set up rich edit control - rich_edit_.Attach(GetDlgItem(IDC_RICHEDIT_FORMAT)); - rich_edit_.SetEventMask(ENM_CHANGE); - - // Create preview episode - if (CurrentEpisode.anime_id > 0) { - PreviewEpisode = CurrentEpisode; - } else { - PreviewEpisode.anime_id = -4224; - PreviewEpisode.folder = L"D:\\Anime\\"; - PreviewEpisode.file = L"[TaigaSubs]_Toradora!_-_01v2_-_Tiger_and_Dragon_[DVD][1280x720_H264_AAC][ABCD1234].mkv"; - Meow.ExamineTitle(PreviewEpisode.file, PreviewEpisode); - } - // Create preview anime - if (!PreviewAnime.GetId()) { - PreviewAnime.SetId(-4224); - PreviewAnime.SetTitle(L"Toradora!"); - PreviewAnime.SetSynonyms(L"Tiger X Dragon"); - PreviewAnime.SetType(mal::TYPE_TV); - PreviewAnime.SetEpisodeCount(25); - PreviewAnime.SetAiringStatus(mal::STATUS_FINISHED); - PreviewAnime.SetDate(anime::DATE_START, Date(2008, 10, 01)); - PreviewAnime.SetDate(anime::DATE_END, Date(2009, 03, 25)); - PreviewAnime.SetImageUrl(L"http://cdn.myanimelist.net/images/anime/5/22125.jpg"); - PreviewAnime.SetEpisodeCount(26); - PreviewAnime.AddtoUserList(); - PreviewAnime.SetMyLastWatchedEpisode(25); - PreviewAnime.SetMyScore(10); - PreviewAnime.SetMyStatus(mal::MYSTATUS_COMPLETED); - PreviewAnime.SetMyTags(L"comedy, romance, drama"); - } - - // Set text - switch (mode) { - case FORMAT_MODE_HTTP: - SetText(L"Edit format - HTTP request"); - rich_edit_.SetText(Settings.Announce.HTTP.format.c_str()); - break; - case FORMAT_MODE_MESSENGER: - SetText(L"Edit format - Messenger"); - rich_edit_.SetText(Settings.Announce.MSN.format.c_str()); - break; - case FORMAT_MODE_MIRC: - SetText(L"Edit format - mIRC"); - rich_edit_.SetText(Settings.Announce.MIRC.format.c_str()); - break; - case FORMAT_MODE_SKYPE: - SetText(L"Edit format - Skype"); - rich_edit_.SetText(Settings.Announce.Skype.format.c_str()); - break; - case FORMAT_MODE_TWITTER: - SetText(L"Edit format - Twitter"); - rich_edit_.SetText(Settings.Announce.Twitter.format.c_str()); - break; - case FORMAT_MODE_BALLOON: - SetText(L"Edit format - Balloon tooltips"); - rich_edit_.SetText(Settings.Program.Notifications.format.c_str()); - break; - } - - return TRUE; -} - -void FormatDialog::OnOK() { - switch (mode) { - case FORMAT_MODE_HTTP: - rich_edit_.GetText(Settings.Announce.HTTP.format); - break; - case FORMAT_MODE_MESSENGER: - rich_edit_.GetText(Settings.Announce.MSN.format); - break; - case FORMAT_MODE_MIRC: - rich_edit_.GetText(Settings.Announce.MIRC.format); - break; - case FORMAT_MODE_SKYPE: - rich_edit_.GetText(Settings.Announce.Skype.format); - break; - case FORMAT_MODE_TWITTER: - rich_edit_.GetText(Settings.Announce.Twitter.format); - break; - case FORMAT_MODE_BALLOON: - rich_edit_.GetText(Settings.Program.Notifications.format); - break; - } - - EndDialog(IDOK); -} - -BOOL FormatDialog::OnCommand(WPARAM wParam, LPARAM lParam) { - switch (LOWORD(wParam)) { - // Add button - case IDHELP: { - wstring answer = UI.Menus.Show(m_hWindow, 0, 0, L"ScriptAdd"); - wstring str; rich_edit_.GetText(str); - CHARRANGE cr = {0}; - rich_edit_.GetSel(&cr); - str.insert(cr.cpMin, answer); - rich_edit_.SetText(str.c_str()); - rich_edit_.SetFocus(); - return TRUE; - } - } - - if (LOWORD(wParam) == IDC_RICHEDIT_FORMAT && HIWORD(wParam) == EN_CHANGE) { - // Set preview text - RefreshPreviewText(); - // Highlight - ColorizeText(); - return TRUE; - } - return FALSE; -} - -// ============================================================================= - -void FormatDialog::ColorizeText() { - // Save old selection - CHARRANGE cr = {0}; - rich_edit_.GetSel(&cr); - rich_edit_.SetRedraw(FALSE); - rich_edit_.HideSelection(TRUE); - - // Set up character format - CHARFORMAT cf = {0}; - cf.cbSize = sizeof(CHARFORMAT); - cf.dwMask = CFM_COLOR | CFM_FACE | CFM_SIZE; - cf.yHeight = 200; - lstrcpy(cf.szFaceName, L"Courier New"); - - // Get current text - wstring text; rich_edit_.GetText(text); - - // Reset all colors - cf.crTextColor = ::GetSysColor(COLOR_WINDOWTEXT); - rich_edit_.SetCharFormat(SCF_ALL, &cf); - - // Highlight - for (size_t i = 0; i < text.length(); i++) { - switch (text[i]) { - // Highlight functions - case '$': { - cf.crTextColor = RGB(0, 0, 160); - size_t pos = text.find('(', i); - if (pos != wstring::npos) { - if (IsScriptFunction(text.substr(i + 1, pos - (i + 1)))) { - rich_edit_.SetSel(i, pos); - rich_edit_.SetCharFormat(SCF_SELECTION, &cf); - i = pos; - } - } - break; - } - // Highlight keywords - case '%': { - cf.crTextColor = RGB(0, 160, 0); - size_t pos = text.find('%', i + 1); - if (pos != wstring::npos) { - if (IsScriptVariable(text.substr(i + 1, pos - (i + 1)))) { - rich_edit_.SetSel(i, pos + 1); - rich_edit_.SetCharFormat(SCF_SELECTION, &cf); - i = pos; - } - } - break; - } - } - } - - // Reset to old selection - rich_edit_.SetSel(&cr); - rich_edit_.HideSelection(FALSE); - rich_edit_.SetRedraw(TRUE); - rich_edit_.InvalidateRect(); -} - -void FormatDialog::RefreshPreviewText() { - // Replace variables - wstring str; - GetDlgItemText(IDC_RICHEDIT_FORMAT, str); - str = ReplaceVariables(str, PreviewEpisode, false, false, true); - - switch (mode) { - case FORMAT_MODE_MIRC: { - // Strip IRC characters - for (size_t i = 0; i < str.length(); i++) { - if (str[i] == 0x02 || // Bold - str[i] == 0x16 || // Reverse - str[i] == 0x1D || // Italic - str[i] == 0x1F || // Underline - str[i] == 0x0F) { // Disable all - str.erase(i, 1); - i--; continue; - } - // Color code - if (str[i] == 0x03) { - str.erase(i, 1); - if (IsNumeric(str[i])) str.erase(i, 1); - if (IsNumeric(str[i])) str.erase(i, 1); - i--; continue; - } - } - break; - } - case FORMAT_MODE_SKYPE: { - // Strip HTML codes - StripHtmlTags(str); - } - } - - // Set final text - SetDlgItemText(IDC_EDIT_PREVIEW, str.c_str()); -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/html.h" +#include "base/string.h" +#include "taiga/dummy.h" +#include "taiga/resource.h" +#include "taiga/script.h" +#include "taiga/settings.h" +#include "ui/dlg/dlg_format.h" +#include "ui/menu.h" + +namespace ui { + +FormatDialog DlgFormat; + +FormatDialog::FormatDialog() + : mode(kFormatModeHttp) { + RegisterDlgClass(L"TaigaFormatW"); +} + +BOOL FormatDialog::OnInitDialog() { + // Set up rich edit control + rich_edit_.Attach(GetDlgItem(IDC_RICHEDIT_FORMAT)); + rich_edit_.SetEventMask(ENM_CHANGE); + + // Set text + switch (mode) { + case kFormatModeHttp: + SetText(L"Edit format - HTTP request"); + rich_edit_.SetText(Settings[taiga::kShare_Http_Format].c_str()); + break; + case kFormatModeMirc: + SetText(L"Edit format - mIRC"); + rich_edit_.SetText(Settings[taiga::kShare_Mirc_Format].c_str()); + break; + case kFormatModeSkype: + SetText(L"Edit format - Skype"); + rich_edit_.SetText(Settings[taiga::kShare_Skype_Format].c_str()); + break; + case kFormatModeTwitter: + SetText(L"Edit format - Twitter"); + rich_edit_.SetText(Settings[taiga::kShare_Twitter_Format].c_str()); + break; + case kFormatModeBalloon: + SetText(L"Edit format - Balloon tooltips"); + rich_edit_.SetText(Settings[taiga::kSync_Notify_Format].c_str()); + break; + } + + return TRUE; +} + +void FormatDialog::OnOK() { + switch (mode) { + case kFormatModeHttp: + Settings.Set(taiga::kShare_Http_Format, rich_edit_.GetText()); + break; + case kFormatModeMirc: + Settings.Set(taiga::kShare_Mirc_Format, rich_edit_.GetText()); + break; + case kFormatModeSkype: + Settings.Set(taiga::kShare_Skype_Format, rich_edit_.GetText()); + break; + case kFormatModeTwitter: + Settings.Set(taiga::kShare_Twitter_Format, rich_edit_.GetText()); + break; + case kFormatModeBalloon: + Settings.Set(taiga::kSync_Notify_Format, rich_edit_.GetText()); + break; + } + + EndDialog(IDOK); +} + +BOOL FormatDialog::OnCommand(WPARAM wParam, LPARAM lParam) { + switch (LOWORD(wParam)) { + // Add button + case IDHELP: { + std::wstring answer = ui::Menus.Show(GetWindowHandle(), 0, 0, L"ScriptAdd"); + std::wstring str; + rich_edit_.GetText(str); + CHARRANGE cr = {0}; + rich_edit_.GetSel(&cr); + str.insert(cr.cpMin, answer); + rich_edit_.SetText(str.c_str()); + rich_edit_.SetFocus(); + return TRUE; + } + } + + if (LOWORD(wParam) == IDC_RICHEDIT_FORMAT && + HIWORD(wParam) == EN_CHANGE) { + // Set preview text + RefreshPreviewText(); + // Highlight + ColorizeText(); + return TRUE; + } + + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////////// + +void FormatDialog::ColorizeText() { + // Save old selection + CHARRANGE cr = {0}; + rich_edit_.GetSel(&cr); + rich_edit_.SetRedraw(FALSE); + rich_edit_.HideSelection(TRUE); + + // Set up character format + CHARFORMAT cf = {0}; + cf.cbSize = sizeof(CHARFORMAT); + cf.dwMask = CFM_COLOR | CFM_FACE | CFM_SIZE; + cf.yHeight = 200; + lstrcpy(cf.szFaceName, L"Courier New"); + + // Get current text + std::wstring text; + rich_edit_.GetText(text); + + // Reset all colors + cf.crTextColor = ::GetSysColor(COLOR_WINDOWTEXT); + rich_edit_.SetCharFormat(SCF_ALL, &cf); + + // Highlight + for (size_t i = 0; i < text.length(); i++) { + switch (text[i]) { + // Highlight functions + case '$': { + cf.crTextColor = RGB(0, 0, 160); + size_t pos = text.find('(', i); + if (pos != std::wstring::npos) { + if (IsScriptFunction(text.substr(i + 1, pos - (i + 1)))) { + rich_edit_.SetSel(i, pos); + rich_edit_.SetCharFormat(SCF_SELECTION, &cf); + i = pos; + } + } + break; + } + // Highlight keywords + case '%': { + cf.crTextColor = RGB(0, 160, 0); + size_t pos = text.find('%', i + 1); + if (pos != std::wstring::npos) { + if (IsScriptVariable(text.substr(i + 1, pos - (i + 1)))) { + rich_edit_.SetSel(i, pos + 1); + rich_edit_.SetCharFormat(SCF_SELECTION, &cf); + i = pos; + } + } + break; + } + } + } + + // Reset to old selection + rich_edit_.SetSel(&cr); + rich_edit_.HideSelection(FALSE); + rich_edit_.SetRedraw(TRUE); + rich_edit_.InvalidateRect(); +} + +void FormatDialog::RefreshPreviewText() { + // Replace variables + std::wstring str; + GetDlgItemText(IDC_RICHEDIT_FORMAT, str); + anime::Episode* episode = &taiga::DummyEpisode; + if (CurrentEpisode.anime_id > 0) + episode = &CurrentEpisode; + str = ReplaceVariables(str, *episode, false, false, true); + + switch (mode) { + case kFormatModeMirc: { + // Strip IRC characters + for (size_t i = 0; i < str.length(); i++) { + if (str[i] == 0x02 || // Bold + str[i] == 0x16 || // Reverse + str[i] == 0x1D || // Italic + str[i] == 0x1F || // Underline + str[i] == 0x0F) { // Disable all + str.erase(i, 1); + i--; continue; + } + // Color code + if (str[i] == 0x03) { + str.erase(i, 1); + if (IsNumeric(str[i])) + str.erase(i, 1); + if (IsNumeric(str[i])) + str.erase(i, 1); + i--; + continue; + } + } + break; + } + case kFormatModeSkype: { + // Strip HTML codes + StripHtmlTags(str); + break; + } + } + + // Set final text + SetDlgItemText(IDC_EDIT_PREVIEW, str.c_str()); +} + +} // namespace ui \ No newline at end of file diff --git a/dlg/dlg_format.h b/src/ui/dlg/dlg_format.h similarity index 55% rename from dlg/dlg_format.h rename to src/ui/dlg/dlg_format.h index 9e7a701b5..1cb1a4cca 100644 --- a/dlg/dlg_format.h +++ b/src/ui/dlg/dlg_format.h @@ -1,61 +1,57 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_FORMAT_H -#define DLG_FORMAT_H - -#include "../std.h" -#include "../anime_item.h" -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" - -enum { - FORMAT_MODE_HTTP, - FORMAT_MODE_MESSENGER, - FORMAT_MODE_MIRC, - FORMAT_MODE_SKYPE, - FORMAT_MODE_TWITTER, - FORMAT_MODE_BALLOON -}; - -// ============================================================================= - -class FormatDialog : public win32::Dialog { -public: - FormatDialog(); - ~FormatDialog() {} - - BOOL OnCommand(WPARAM wParam, LPARAM lParam); - BOOL OnInitDialog(); - void OnOK(); - -public: - void ColorizeText(); - void RefreshPreviewText(); - -public: - int mode; - -private: - win32::RichEdit rich_edit_; -}; - -extern class FormatDialog FormatDialog; -extern anime::Item PreviewAnime; - -#endif // DLG_FORMAT_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_FORMAT_H +#define TAIGA_UI_DLG_FORMAT_H + +#include "win/ctrl/win_ctrl.h" +#include "win/win_dialog.h" + +namespace ui { + +enum FormatModes { + kFormatModeHttp, + kFormatModeMirc, + kFormatModeSkype, + kFormatModeTwitter, + kFormatModeBalloon +}; + +class FormatDialog : public win::Dialog { +public: + FormatDialog(); + ~FormatDialog() {} + + BOOL OnCommand(WPARAM wParam, LPARAM lParam); + BOOL OnInitDialog(); + void OnOK(); + + void ColorizeText(); + void RefreshPreviewText(); + + int mode; + +private: + win::RichEdit rich_edit_; +}; + +extern FormatDialog DlgFormat; + +} // namespace ui + +#endif // TAIGA_UI_DLG_FORMAT_H \ No newline at end of file diff --git a/dlg/dlg_history.cpp b/src/ui/dlg/dlg_history.cpp similarity index 65% rename from dlg/dlg_history.cpp rename to src/ui/dlg/dlg_history.cpp index 9e3e3eae3..7533dd163 100644 --- a/dlg/dlg_history.cpp +++ b/src/ui/dlg/dlg_history.cpp @@ -1,290 +1,303 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_anime_info.h" -#include "dlg_anime_list.h" -#include "dlg_history.h" -#include "dlg_main.h" - -#include "../anime_db.h" -#include "../common.h" -#include "../foreach.h" -#include "../gfx.h" -#include "../history.h" -#include "../myanimelist.h" -#include "../resource.h" -#include "../settings.h" -#include "../string.h" -#include "../taiga.h" -#include "../theme.h" - -class HistoryDialog HistoryDialog; - -// ============================================================================= - -BOOL HistoryDialog::OnInitDialog() { - // Create list - list_.Attach(GetDlgItem(IDC_LIST_EVENT)); - list_.EnableGroupView(true); - list_.SetExtendedStyle(LVS_EX_AUTOSIZECOLUMNS | LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP); - list_.SetTheme(); - - // Insert list columns - list_.InsertColumn(0, GetSystemMetrics(SM_CXSCREEN), 250, LVCFMT_LEFT, L"Anime title"); - list_.InsertColumn(1, 400, 400, LVCFMT_LEFT, L"Details"); - list_.InsertColumn(2, 120, 120, LVCFMT_LEFT, L"Last modified"); - list_.SetColumnWidth(2, LVSCW_AUTOSIZE_USEHEADER); - - // Insert list groups - list_.InsertGroup(0, L"Queued for update"); - list_.InsertGroup(1, L"Recently watched"); - - // Refresh list - RefreshList(); - return TRUE; -} - -INT_PTR HistoryDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - // Forward mouse wheel messages to the list - case WM_MOUSEWHEEL: { - return list_.SendMessage(uMsg, wParam, lParam); - } - } - - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - - -LRESULT HistoryDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { - if (pnmh->hwndFrom == list_.GetWindowHandle()) { - switch (pnmh->code) { - // Custom draw - case NM_CUSTOMDRAW: { - LPNMLVCUSTOMDRAW pCD = reinterpret_cast(pnmh); - switch (pCD->nmcd.dwDrawStage) { - case CDDS_PREPAINT: - return CDRF_NOTIFYITEMDRAW; - case CDDS_ITEMPREPAINT: - return CDRF_NOTIFYSUBITEMDRAW; - case CDDS_ITEMPREPAINT | CDDS_SUBITEM: - // Alternate background color - if (pCD->nmcd.dwItemSpec % 2) - pCD->clrTextBk = ChangeColorBrightness(GetSysColor(COLOR_WINDOW), -0.03f); - return CDRF_DODEFAULT; - } - break; - } - // Double click - case NM_DBLCLK: { - if (list_.GetSelectedCount() > 0) { - auto lpnmitem = reinterpret_cast(pnmh); - int item_index = list_.GetNextItem(-1, LVNI_SELECTED); - int anime_id = list_.GetItemParam(item_index); - ExecuteAction(L"Info", 0, anime_id); - } - break; - } - // Right click - case NM_RCLICK: { - wstring action = UI.Menus.Show(g_hMain, 0, 0, L"HistoryList"); - if (action == L"Delete()") { - RemoveItems(); - } else if (action == L"ClearHistory()") { - History.items.clear(); - History.Save(); - RefreshList(); - } - break; - } - } - } - - return 0; -} - -void HistoryDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { - switch (uMsg) { - case WM_SIZE: { - win32::Rect rcWindow(0, 0, size.cx, size.cy); - list_.SetPosition(nullptr, rcWindow, 0); - break; - } - } -} - -BOOL HistoryDialog::PreTranslateMessage(MSG* pMsg) { - switch (pMsg->message) { - case WM_KEYDOWN: { - if (::GetFocus() == list_.GetWindowHandle()) { - switch (pMsg->wParam) { - // Select all items - case 'A': { - if (::GetKeyState(VK_CONTROL) & 0xFF80) { - list_.SetSelectedItem(-1); - return TRUE; - } - break; - } - // Delete selected items - case VK_DELETE: { - if (RemoveItems()) { - return TRUE; - } - } - // Move selected items - case VK_UP: - case VK_DOWN: { - if (::GetKeyState(VK_CONTROL) & 0xFF80) { - if (MoveItems(pMsg->wParam == VK_UP ? -1 : 1)) { - return TRUE; - } - } - break; - } - } - } - break; - } - } - - return FALSE; -} - -// ============================================================================= - -void HistoryDialog::RefreshList() { - if (!IsWindow()) return; - - // Clear list - list_.Hide(); - list_.DeleteAllItems(); - - // Add queued items - foreach_cr_(it, History.queue.items) { - int i = list_.GetItemCount(); - list_.InsertItem(i, 0, -1, 0, nullptr, - AnimeDatabase.FindItem(it->anime_id)->GetTitle().c_str(), - static_cast(it->anime_id)); - wstring details; - if (it->mode == HTTP_MAL_AnimeAdd) - AppendString(details, L"Add to list"); - if (it->mode == HTTP_MAL_AnimeDelete) - AppendString(details, L"Remove from list"); - if (it->episode) - AppendString(details, L"Episode: " + mal::TranslateNumber(*it->episode)); - if (it->score) - AppendString(details, L"Score: " + mal::TranslateNumber(*it->score)); - if (it->status) - AppendString(details, !it->enable_rewatching || *it->enable_rewatching != TRUE ? - L"Status: " + mal::TranslateMyStatus(*it->status, false) : L"Re-watching"); - if (it->tags) - AppendString(details, L"Tags: \"" + *it->tags + L"\""); - if (it->date_start) - AppendString(details, L"Start date: " + - wstring(mal::TranslateDateFromApi(*it->date_start))); - if (it->date_finish) - AppendString(details, L"Finish date: " + - wstring(mal::TranslateDateFromApi(*it->date_finish))); - list_.SetItem(i, 1, details.c_str()); - list_.SetItem(i, 2, it->time.c_str()); - } - - // Add recently watched - foreach_cr_(it, History.items) { - int i = list_.GetItemCount(); - list_.InsertItem(i, 1, -1, 0, nullptr, - AnimeDatabase.FindItem(it->anime_id)->GetTitle().c_str(), - static_cast(it->anime_id)); - wstring details; - AppendString(details, L"Episode: " + mal::TranslateNumber(*it->episode)); - list_.SetItem(i, 1, details.c_str()); - list_.SetItem(i, 2, it->time.c_str()); - } - - list_.Show(); -} - -bool HistoryDialog::MoveItems(int pos) { - // TODO: Re-enable - return false; - - if (History.queue.updating) { - MessageBox(L"History cannot be modified while an update is in progress.", L"Error", MB_ICONERROR); - return false; - } - - int index = -1; - vector item_selected(list_.GetItemCount()); - vector item_selected_new(list_.GetItemCount()); - while ((index = list_.GetNextItem(index, LVNI_SELECTED)) > -1) { - item_selected.at(index) = true; - } - - for (size_t i = 0; i < item_selected.size(); i++) { - size_t j = (pos < 0 ? i : item_selected.size() - 1 - i); - if (!item_selected.at(j)) continue; - if (j == (pos < 0 ? 0 : item_selected.size() - 1)) { item_selected_new.at(j) = true; continue; } - if (item_selected_new.at(j + pos)) { item_selected_new.at(j) = true; continue; } - std::iter_swap(History.queue.items.begin() + j, History.queue.items.begin() + j + pos); - item_selected_new.at(j + pos) = true; - } - - RefreshList(); - for (size_t i = 0; i < item_selected_new.size(); i++) { - if (item_selected_new.at(i)) list_.SetSelectedItem(i); - } - - return true; -} - -bool HistoryDialog::RemoveItems() { - if (History.queue.updating) { - MessageBox(L"History cannot be modified while an update is in progress.", L"Error", MB_ICONERROR); - return false; - } - - if (list_.GetSelectedCount() > 0) { - while (list_.GetSelectedCount() > 0) { - int item_index = list_.GetNextItem(-1, LVNI_SELECTED); - list_.DeleteItem(item_index); - if (item_index < static_cast(History.queue.items.size())) { - item_index = History.queue.items.size() - item_index - 1; - History.queue.Remove(item_index, false, false, false); - } else { - item_index -= History.queue.items.size(); - item_index = History.items.size() - item_index - 1; - History.items.erase(History.items.begin() + item_index); - } - } - History.Save(); - } else { - History.queue.Clear(); - } - - RefreshList(); - - MainDialog.treeview.RefreshHistoryCounter(); - NowPlayingDialog.Refresh(false, false, false); - AnimeListDialog.RefreshList(); - AnimeListDialog.RefreshTabs(); - - return true; -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "base/gfx.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "library/history.h" +#include "taiga/http.h" +#include "taiga/resource.h" +#include "taiga/taiga.h" +#include "ui/dlg/dlg_history.h" +#include "ui/dlg/dlg_main.h" +#include "ui/dialog.h" +#include "ui/menu.h" +#include "ui/theme.h" +#include "ui/ui.h" + +namespace ui { + +HistoryDialog DlgHistory; + +BOOL HistoryDialog::OnInitDialog() { + // Create list + list_.Attach(GetDlgItem(IDC_LIST_EVENT)); + list_.EnableGroupView(true); + list_.SetExtendedStyle(LVS_EX_AUTOSIZECOLUMNS | LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP); + list_.SetImageList(ui::Theme.GetImageList16().GetHandle()); + list_.SetTheme(); + + // Insert list columns + list_.InsertColumn(0, GetSystemMetrics(SM_CXSCREEN), 250, LVCFMT_LEFT, L"Anime title"); + list_.InsertColumn(1, 400, 400, LVCFMT_LEFT, L"Details"); + list_.InsertColumn(2, 120, 120, LVCFMT_LEFT, L"Last modified"); + list_.SetColumnWidth(2, LVSCW_AUTOSIZE_USEHEADER); + + // Insert list groups + list_.InsertGroup(0, L"Queued for update"); + list_.InsertGroup(1, L"Recently watched"); + + // Refresh list + RefreshList(); + return TRUE; +} + +INT_PTR HistoryDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + // Forward mouse wheel messages to the list + case WM_MOUSEWHEEL: { + return list_.SendMessage(uMsg, wParam, lParam); + } + } + + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + + +LRESULT HistoryDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { + if (pnmh->hwndFrom == list_.GetWindowHandle()) { + switch (pnmh->code) { + // Custom draw + case NM_CUSTOMDRAW: { + LPNMLVCUSTOMDRAW pCD = reinterpret_cast(pnmh); + switch (pCD->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + return CDRF_NOTIFYITEMDRAW; + case CDDS_ITEMPREPAINT: + return CDRF_NOTIFYSUBITEMDRAW; + case CDDS_ITEMPREPAINT | CDDS_SUBITEM: + // Alternate background color + if (pCD->nmcd.dwItemSpec % 2) + pCD->clrTextBk = ChangeColorBrightness(GetSysColor(COLOR_WINDOW), -0.03f); + return CDRF_DODEFAULT; + } + break; + } + // Double click + case NM_DBLCLK: { + if (list_.GetSelectedCount() > 0) { + auto lpnmitem = reinterpret_cast(pnmh); + int item_index = list_.GetNextItem(-1, LVNI_SELECTED); + int anime_id = list_.GetItemParam(item_index); + ShowDlgAnimeInfo(anime_id); + } + break; + } + // Right click + case NM_RCLICK: { + std::wstring action = ui::Menus.Show(DlgMain.GetWindowHandle(), 0, 0, L"HistoryList"); + if (action == L"Delete()") { + RemoveItems(); + } else if (action == L"ClearHistory()") { + History.Clear(); + } + break; + } + } + } + + return 0; +} + +void HistoryDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { + switch (uMsg) { + case WM_SIZE: { + win::Rect rcWindow(0, 0, size.cx, size.cy); + list_.SetPosition(nullptr, rcWindow, 0); + break; + } + } +} + +BOOL HistoryDialog::PreTranslateMessage(MSG* pMsg) { + switch (pMsg->message) { + case WM_KEYDOWN: { + if (::GetFocus() == list_.GetWindowHandle()) { + switch (pMsg->wParam) { + // Select all items + case 'A': { + if (::GetKeyState(VK_CONTROL) & 0xFF80) { + list_.SetSelectedItem(-1); + return TRUE; + } + break; + } + // Delete selected items + case VK_DELETE: { + if (RemoveItems()) + return TRUE; + } + // Move selected items + case VK_UP: + case VK_DOWN: { + if (::GetKeyState(VK_CONTROL) & 0xFF80) + if (MoveItems(pMsg->wParam == VK_UP ? -1 : 1)) + return TRUE; + break; + } + } + } + break; + } + } + + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////////// + +void HistoryDialog::RefreshList() { + if (!IsWindow()) + return; + + // Clear list + list_.Hide(); + list_.DeleteAllItems(); + + // Add queued items + foreach_cr_(it, History.queue.items) { + int i = list_.GetItemCount(); + + int icon = ui::kIcon16_ArrowUp; + switch (it->mode) { + case taiga::kHttpServiceAddLibraryEntry: + icon = ui::kIcon16_Plus; + break; + case taiga::kHttpServiceDeleteLibraryEntry: + icon = ui::kIcon16_Cross; + break; + } + + std::wstring details; + if (it->mode == taiga::kHttpServiceAddLibraryEntry) + AppendString(details, L"Add to list"); + if (it->mode == taiga::kHttpServiceDeleteLibraryEntry) + AppendString(details, L"Remove from list"); + if (it->episode) + AppendString(details, L"Episode: " + anime::TranslateNumber(*it->episode)); + if (it->score) + AppendString(details, L"Score: " + anime::TranslateNumber(*it->score)); + if (it->status) + AppendString(details, !it->enable_rewatching || *it->enable_rewatching != TRUE ? + L"Status: " + anime::TranslateMyStatus(*it->status, false) : L"Re-watching"); + if (it->tags) + AppendString(details, L"Tags: \"" + *it->tags + L"\""); + if (it->date_start) + AppendString(details, L"Start date: " + std::wstring(*it->date_start)); + if (it->date_finish) + AppendString(details, L"Finish date: " + std::wstring(*it->date_finish)); + + list_.InsertItem(i, 0, icon, 0, nullptr, + AnimeDatabase.FindItem(it->anime_id)->GetTitle().c_str(), + static_cast(it->anime_id)); + list_.SetItem(i, 1, details.c_str()); + list_.SetItem(i, 2, it->time.c_str()); + } + + // Add recently watched + foreach_cr_(it, History.items) { + int i = list_.GetItemCount(); + auto anime_item = AnimeDatabase.FindItem(it->anime_id); + int icon = StatusToIcon(anime_item->GetAiringStatus()); + std::wstring details; + AppendString(details, L"Episode: " + anime::TranslateNumber(*it->episode)); + + list_.InsertItem(i, 1, icon, 0, nullptr, + anime_item->GetTitle().c_str(), + static_cast(it->anime_id)); + list_.SetItem(i, 1, details.c_str()); + list_.SetItem(i, 2, it->time.c_str()); + } + + list_.Show(); +} + +bool HistoryDialog::MoveItems(int pos) { + // TODO: Re-enable + return false; + + if (History.queue.updating) { + MessageBox(L"History cannot be modified while an update is in progress.", + L"Error", MB_ICONERROR); + return false; + } + + int index = -1; + std::vector item_selected(list_.GetItemCount()); + std::vector item_selected_new(list_.GetItemCount()); + while ((index = list_.GetNextItem(index, LVNI_SELECTED)) > -1) { + item_selected.at(index) = true; + } + + for (size_t i = 0; i < item_selected.size(); i++) { + size_t j = (pos < 0 ? i : item_selected.size() - 1 - i); + if (!item_selected.at(j)) + continue; + if (j == (pos < 0 ? 0 : item_selected.size() - 1)) { + item_selected_new.at(j) = true; + continue; + } + if (item_selected_new.at(j + pos)) { + item_selected_new.at(j) = true; + continue; + } + std::iter_swap(History.queue.items.begin() + j, + History.queue.items.begin() + j + pos); + item_selected_new.at(j + pos) = true; + } + + RefreshList(); + for (size_t i = 0; i < item_selected_new.size(); i++) + if (item_selected_new.at(i)) list_.SetSelectedItem(i); + + return true; +} + +bool HistoryDialog::RemoveItems() { + if (History.queue.updating) { + MessageBox(L"History cannot be modified while an update is in progress.", + L"Error", MB_ICONERROR); + return false; + } + + if (list_.GetSelectedCount() > 0) { + while (list_.GetSelectedCount() > 0) { + int item_index = list_.GetNextItem(-1, LVNI_SELECTED); + list_.DeleteItem(item_index); + if (item_index < static_cast(History.queue.items.size())) { + item_index = History.queue.items.size() - item_index - 1; + History.queue.Remove(item_index, false, false, false); + } else { + item_index -= History.queue.items.size(); + item_index = History.items.size() - item_index - 1; + History.items.erase(History.items.begin() + item_index); + } + } + History.Save(); + } else { + History.queue.Clear(); + } + + ui::OnHistoryChange(); + + return true; +} + +} // namespace ui diff --git a/dlg/dlg_history.h b/src/ui/dlg/dlg_history.h similarity index 66% rename from dlg/dlg_history.h rename to src/ui/dlg/dlg_history.h index 58ae87357..505de4a66 100644 --- a/dlg/dlg_history.h +++ b/src/ui/dlg/dlg_history.h @@ -1,50 +1,50 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_HISTORY_H -#define DLG_HISTORY_H - -#include "../std.h" -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" - -// ============================================================================= - -class HistoryDialog : public win32::Dialog { -public: - HistoryDialog() {} - virtual ~HistoryDialog() {} - - INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - BOOL OnInitDialog(); - LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); - void OnSize(UINT uMsg, UINT nType, SIZE size); - BOOL PreTranslateMessage(MSG* pMsg); - -public: - void RefreshList(); - bool MoveItems(int pos); - bool RemoveItems(); - -private: - win32::ListView list_; -}; - -extern class HistoryDialog HistoryDialog; - -#endif // DLG_HISTORY_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_HISTORY_H +#define TAIGA_UI_DLG_HISTORY_H + +#include "win/ctrl/win_ctrl.h" +#include "win/win_dialog.h" + +namespace ui { + +class HistoryDialog : public win::Dialog { +public: + HistoryDialog() {} + ~HistoryDialog() {} + + INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + BOOL OnInitDialog(); + LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); + void OnSize(UINT uMsg, UINT nType, SIZE size); + BOOL PreTranslateMessage(MSG* pMsg); + + void RefreshList(); + bool MoveItems(int pos); + bool RemoveItems(); + +private: + win::ListView list_; +}; + +extern HistoryDialog DlgHistory; + +} // namespace ui + +#endif // TAIGA_UI_DLG_HISTORY_H \ No newline at end of file diff --git a/dlg/dlg_input.cpp b/src/ui/dlg/dlg_input.cpp similarity index 75% rename from dlg/dlg_input.cpp rename to src/ui/dlg/dlg_input.cpp index 7e23d689f..2d3fcdde6 100644 --- a/dlg/dlg_input.cpp +++ b/src/ui/dlg/dlg_input.cpp @@ -1,82 +1,81 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_input.h" - -#include "../resource.h" - -// ============================================================================= - -InputDialog::InputDialog() : - current_value_(0), min_value_(0), max_value_(0), - numbers_only_(false), result(0) -{ - info = L"Set new value:"; - title = L"Input"; -}; - -void InputDialog::SetNumbers(bool enabled, int min_value, int max_value, int current_value) { - numbers_only_ = enabled; - min_value_ = min_value; - max_value_ = max_value; - current_value_ = current_value; -} - -void InputDialog::Show(HWND parent) { - result = Create(IDD_INPUT, parent, true); -} - -// ============================================================================= - -BOOL InputDialog::OnInitDialog() { - // Set dialog title - SetWindowText(m_hWindow, title.c_str()); - - // Set information text - SetDlgItemText(IDC_STATIC_INPUTINFO, info.c_str()); - - // Set text style and properties - edit_.Attach(GetDlgItem(IDC_EDIT_INPUT)); - spin_.Attach(GetDlgItem(IDC_SPIN_INPUT)); - edit_.LimitText(256); - if (numbers_only_) { - edit_.SetStyle(ES_NUMBER, 0); - spin_.SetBuddy(edit_.GetWindowHandle()); - spin_.SetRange32(min_value_, max_value_ > 0 ? max_value_ : 9999); - spin_.SetPos32(current_value_); - } else { - edit_.SetStyle(0, ES_NUMBER); - edit_.SetText(text.c_str()); - spin_.Enable(FALSE); - spin_.Hide(); - } - edit_.SetSel(0, -1); - - return TRUE; -} - -void InputDialog::OnCancel() { - EndDialog(IDCANCEL); -} - -void InputDialog::OnOK() { - edit_.GetText(text); - EndDialog(IDOK); -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "taiga/resource.h" +#include "ui/dlg/dlg_input.h" + +namespace ui { + +InputDialog::InputDialog() + : current_value_(0), min_value_(0), max_value_(0), + numbers_only_(false), result(0) { + info = L"Set new value:"; + title = L"Input"; +}; + +void InputDialog::SetNumbers(bool enabled, int min_value, int max_value, + int current_value) { + numbers_only_ = enabled; + min_value_ = min_value; + max_value_ = max_value; + current_value_ = current_value; +} + +void InputDialog::Show(HWND parent) { + result = Create(IDD_INPUT, parent, true); +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL InputDialog::OnInitDialog() { + // Set dialog title + SetWindowText(GetWindowHandle(), title.c_str()); + + // Set information text + SetDlgItemText(IDC_STATIC_INPUTINFO, info.c_str()); + + // Set text style and properties + edit_.Attach(GetDlgItem(IDC_EDIT_INPUT)); + spin_.Attach(GetDlgItem(IDC_SPIN_INPUT)); + edit_.LimitText(256); + if (numbers_only_) { + edit_.SetStyle(ES_NUMBER, 0); + spin_.SetBuddy(edit_.GetWindowHandle()); + spin_.SetRange32(min_value_, max_value_ > 0 ? max_value_ : 9999); + spin_.SetPos32(current_value_); + } else { + edit_.SetStyle(0, ES_NUMBER); + edit_.SetText(text.c_str()); + spin_.Enable(FALSE); + spin_.Hide(); + } + edit_.SetSel(0, -1); + + return TRUE; +} + +void InputDialog::OnCancel() { + EndDialog(IDCANCEL); +} + +void InputDialog::OnOK() { + edit_.GetText(text); + EndDialog(IDOK); +} + +} // namespace ui \ No newline at end of file diff --git a/dlg/dlg_input.h b/src/ui/dlg/dlg_input.h similarity index 64% rename from dlg/dlg_input.h rename to src/ui/dlg/dlg_input.h index 9ba289688..e6bf8b994 100644 --- a/dlg/dlg_input.h +++ b/src/ui/dlg/dlg_input.h @@ -1,52 +1,53 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_INPUT_H -#define DLG_INPUT_H - -#include "../std.h" -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" - -// ============================================================================= - -class InputDialog : public win32::Dialog { -public: - InputDialog(); - virtual ~InputDialog() {} - - void OnCancel(); - BOOL OnInitDialog(); - void OnOK(); - -public: - void SetNumbers(bool enabled, int min_value, int max_value, int current_value); - void Show(HWND parent = nullptr); - -public: - INT_PTR result; - wstring info, title, text; - -private: - int current_value_, min_value_, max_value_; - bool numbers_only_; - win32::Edit edit_; - win32::Spin spin_; -}; - -#endif // DLG_INPUT_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_INPUT_H +#define TAIGA_UI_DLG_INPUT_H + +#include + +#include "win/ctrl/win_ctrl.h" +#include "win/win_dialog.h" + +namespace ui { + +class InputDialog : public win::Dialog { +public: + InputDialog(); + ~InputDialog() {} + + void OnCancel(); + BOOL OnInitDialog(); + void OnOK(); + + void SetNumbers(bool enabled, int min_value, int max_value, int current_value); + void Show(HWND parent = nullptr); + + INT_PTR result; + std::wstring info, title, text; + +private: + int current_value_, min_value_, max_value_; + bool numbers_only_; + win::Edit edit_; + win::Spin spin_; +}; + +} // namespace ui + +#endif // TAIGA_UI_DLG_INPUT_H \ No newline at end of file diff --git a/src/ui/dlg/dlg_main.cpp b/src/ui/dlg/dlg_main.cpp new file mode 100644 index 000000000..812fed248 --- /dev/null +++ b/src/ui/dlg/dlg_main.cpp @@ -0,0 +1,848 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/process.h" +#include "base/string.h" +#include "library/anime.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "library/history.h" +#include "library/resource.h" +#include "sync/service.h" +#include "sync/sync.h" +#include "taiga/announce.h" +#include "taiga/resource.h" +#include "taiga/script.h" +#include "taiga/settings.h" +#include "taiga/stats.h" +#include "taiga/taiga.h" +#include "taiga/timer.h" +#include "track/media.h" +#include "track/monitor.h" +#include "track/recognition.h" +#include "track/search.h" +#include "ui/dialog.h" +#include "ui/dlg/dlg_anime_info.h" +#include "ui/dlg/dlg_anime_list.h" +#include "ui/dlg/dlg_history.h" +#include "ui/dlg/dlg_main.h" +#include "ui/dlg/dlg_search.h" +#include "ui/dlg/dlg_season.h" +#include "ui/dlg/dlg_settings.h" +#include "ui/dlg/dlg_stats.h" +#include "ui/dlg/dlg_torrent.h" +#include "ui/dlg/dlg_update.h" +#include "ui/menu.h" +#include "ui/theme.h" +#include "win/win_taskbar.h" +#include "win/win_taskdialog.h" + +namespace ui { + +MainDialog DlgMain; + +MainDialog::MainDialog() { + navigation.parent = this; + search_bar.parent = this; + + RegisterDlgClass(L"TaigaMainW"); +} + +BOOL MainDialog::OnInitDialog() { + // Initialize window properties + InitWindowPosition(); + SetIconLarge(IDI_MAIN); + SetIconSmall(IDI_MAIN); + + // Create default brushes and fonts + ui::Theme.CreateBrushes(); + ui::Theme.CreateFonts(GetDC()); + + // Create controls + CreateDialogControls(); + + // Select default content page + navigation.SetCurrentPage(kSidebarItemAnimeList); + + // Start process timer + taiga::timers.Initialize(); + + // Add icon to taskbar + Taskbar.Create(GetWindowHandle(), nullptr, TAIGA_APP_TITLE); + + ChangeStatus(); + UpdateTip(); + UpdateTitle(); + + // Refresh menus + ui::Menus.UpdateAll(); + + // Apply start-up settings + if (Settings.GetBool(taiga::kSync_AutoOnStart)) { + sync::Synchronize(); + } + if (Settings.GetBool(taiga::kApp_Behavior_ScanAvailableEpisodes)) { + ScanAvailableEpisodesQuick(); + } + if (!Settings.GetBool(taiga::kApp_Behavior_StartMinimized)) { + Show(Settings.GetBool(taiga::kApp_Position_Remember) && Settings.GetBool(taiga::kApp_Position_Maximized) ? + SW_MAXIMIZE : SW_SHOWNORMAL); + } + if (taiga::GetCurrentUsername().empty()) { + win::TaskDialog dlg(TAIGA_APP_TITLE, TD_ICON_INFORMATION); + dlg.SetMainInstruction(L"Welcome to Taiga!"); + dlg.SetContent(L"Username is not set. Would you like to open settings window to set it now?"); + dlg.AddButton(L"Yes", IDYES); + dlg.AddButton(L"No", IDNO); + dlg.Show(GetWindowHandle()); + if (dlg.GetSelectedButtonID() == IDYES) + ShowDlgSettings(kSettingsSectionServices, kSettingsPageServicesMain); + } + if (Settings.GetBool(taiga::kLibrary_WatchFolders)) { + FolderMonitor.SetWindowHandle(GetWindowHandle()); + FolderMonitor.Enable(); + } + + // Success + return TRUE; +} + +void MainDialog::CreateDialogControls() { + // Create rebar + rebar.Attach(GetDlgItem(IDC_REBAR_MAIN)); + // Create menu toolbar + toolbar_menu.Attach(GetDlgItem(IDC_TOOLBAR_MENU)); + toolbar_menu.SetImageList(nullptr, 0, 0); + // Create main toolbar + toolbar_main.Attach(GetDlgItem(IDC_TOOLBAR_MAIN)); + toolbar_main.SetImageList(ui::Theme.GetImageList24().GetHandle(), 24, 24); + toolbar_main.SendMessage(TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS | TBSTYLE_EX_MIXEDBUTTONS); + // Create search toolbar + toolbar_search.Attach(GetDlgItem(IDC_TOOLBAR_SEARCH)); + toolbar_search.SetImageList(ui::Theme.GetImageList24().GetHandle(), 24, 24); + toolbar_search.SendMessage(TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS | TBSTYLE_EX_MIXEDBUTTONS); + // Create search text + edit.Attach(GetDlgItem(IDC_EDIT_SEARCH)); + edit.SetCueBannerText(L"Search list"); + edit.SetMargins(1, 16); + edit.SetParent(toolbar_search.GetWindowHandle()); + win::Rect rcEdit; edit.GetRect(&rcEdit); + win::Rect rcEditWindow; edit.GetWindowRect(&rcEditWindow); + win::Rect rcToolbar; toolbar_search.GetClientRect(&rcToolbar); + int edit_y = (30 /*rcToolbar.Height()*/ - rcEditWindow.Height()) / 2; + edit.SetPosition(nullptr, 0, edit_y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + // Create cancel search button + cancel_button.Attach(GetDlgItem(IDC_BUTTON_CANCELSEARCH)); + cancel_button.SetParent(edit.GetWindowHandle()); + cancel_button.SetPosition(nullptr, rcEdit.right + 1, 0, 16, 16); + // Create treeview control + treeview.Attach(GetDlgItem(IDC_TREE_MAIN)); + treeview.SendMessage(TVM_SETBKCOLOR, 0, ::GetSysColor(COLOR_3DFACE)); + treeview.SetImageList(ui::Theme.GetImageList16().GetHandle()); + treeview.SetItemHeight(20); + treeview.SetTheme(); + if (Settings.GetBool(taiga::kApp_Option_HideSidebar)) { + treeview.Hide(); + } + // Create status bar + statusbar.Attach(GetDlgItem(IDC_STATUSBAR_MAIN)); + statusbar.SetImageList(ui::Theme.GetImageList16().GetHandle()); + statusbar.InsertPart(-1, 0, 0, 900, nullptr, nullptr); + statusbar.InsertPart(ui::kIcon16_Clock, 0, 0, 32, nullptr, nullptr); + + // Insert treeview items + treeview.hti.push_back(treeview.InsertItem(L"Now Playing", ui::kIcon16_Play, kSidebarItemNowPlaying, nullptr)); + treeview.hti.push_back(treeview.InsertItem(nullptr, -1, -1, nullptr)); + treeview.hti.push_back(treeview.InsertItem(L"Anime List", ui::kIcon16_DocumentA, kSidebarItemAnimeList, nullptr)); + treeview.hti.push_back(treeview.InsertItem(L"History", ui::kIcon16_Clock, kSidebarItemHistory, nullptr)); + treeview.hti.push_back(treeview.InsertItem(L"Statistics", ui::kIcon16_Chart, kSidebarItemStats, nullptr)); + treeview.hti.push_back(treeview.InsertItem(nullptr, -1, -1, nullptr)); + treeview.hti.push_back(treeview.InsertItem(L"Search", ui::kIcon16_Search, kSidebarItemSearch, nullptr)); + treeview.hti.push_back(treeview.InsertItem(L"Seasons", ui::kIcon16_Calendar, kSidebarItemSeasons, nullptr)); + treeview.hti.push_back(treeview.InsertItem(L"Torrents", ui::kIcon16_Feed, kSidebarItemFeeds, nullptr)); + if (History.queue.GetItemCount() > 0) { + treeview.RefreshHistoryCounter(); + } + + // Insert menu toolbar buttons + BYTE fsState = TBSTATE_ENABLED; + BYTE fsStyle0 = BTNS_AUTOSIZE | BTNS_DROPDOWN | BTNS_SHOWTEXT; + toolbar_menu.InsertButton(0, I_IMAGENONE, 100, fsState, fsStyle0, 0, L" File", nullptr); + toolbar_menu.InsertButton(1, I_IMAGENONE, 101, fsState, fsStyle0, 0, L" Services", nullptr); + toolbar_menu.InsertButton(2, I_IMAGENONE, 102, fsState, fsStyle0, 0, L" Tools", nullptr); + toolbar_menu.InsertButton(3, I_IMAGENONE, 103, fsState, fsStyle0, 0, L" View", nullptr); + toolbar_menu.InsertButton(4, I_IMAGENONE, 104, fsState, fsStyle0, 0, L" Help", nullptr); + // Insert main toolbar buttons + BYTE fsStyle1 = BTNS_AUTOSIZE; + BYTE fsStyle2 = BTNS_AUTOSIZE | BTNS_WHOLEDROPDOWN; + toolbar_main.InsertButton(0, ui::kIcon24_Sync, kToolbarButtonSync, + fsState, fsStyle1, 0, nullptr, L"Synchronize list"); + toolbar_main.InsertButton(1, 0, 0, 0, BTNS_SEP, 0, nullptr, nullptr); + toolbar_main.InsertButton(2, ui::kIcon24_Folders, kToolbarButtonFolders, + fsState, fsStyle2, 2, nullptr, L"Root folders"); + toolbar_main.InsertButton(3, ui::kIcon24_Tools, kToolbarButtonTools, + fsState, fsStyle2, 3, nullptr, L"External links"); + toolbar_main.InsertButton(4, 0, 0, 0, BTNS_SEP, 0, nullptr, nullptr); + toolbar_main.InsertButton(5, ui::kIcon24_Settings, kToolbarButtonSettings, + fsState, fsStyle1, 5, nullptr, L"Change program settings"); +#ifdef _DEBUG + toolbar_main.InsertButton(6, 0, 0, 0, BTNS_SEP, 0, nullptr, nullptr); + toolbar_main.InsertButton(7, ui::kIcon24_About, kToolbarButtonDebug, + fsState, fsStyle1, 7, nullptr, L"Debug"); +#endif + + // Insert rebar bands + UINT fMask = RBBIM_CHILD | RBBIM_CHILDSIZE | RBBIM_HEADERSIZE | RBBIM_SIZE | RBBIM_STYLE; + UINT fStyle = RBBS_NOGRIPPER; + rebar.InsertBand(toolbar_menu.GetWindowHandle(), + GetSystemMetrics(SM_CXSCREEN), + 0, 0, 0, 0, 0, 0, + HIWORD(toolbar_menu.GetButtonSize()), + fMask, fStyle); + rebar.InsertBand(toolbar_main.GetWindowHandle(), + GetSystemMetrics(SM_CXSCREEN), + win::kControlMargin, 0, 0, 0, 0, 0, + HIWORD(toolbar_main.GetButtonSize()) + 2, + fMask, fStyle | RBBS_BREAK); + rebar.InsertBand(toolbar_search.GetWindowHandle(), + 0, win::kControlMargin, 0, rcEditWindow.Width() + (win::kControlMargin * 2), 0, 0, 0, + HIWORD(toolbar_search.GetButtonSize()), + fMask, fStyle); +} + +void MainDialog::InitWindowPosition() { + UINT flags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER; + const LONG min_w = ScaleX(710); + const LONG min_h = ScaleX(480); + + win::Rect rcParent, rcWindow; + ::GetWindowRect(GetParent(), &rcParent); + rcWindow.Set( + Settings.GetInt(taiga::kApp_Position_X), + Settings.GetInt(taiga::kApp_Position_Y), + Settings.GetInt(taiga::kApp_Position_X) + Settings.GetInt(taiga::kApp_Position_W), + Settings.GetInt(taiga::kApp_Position_Y) + Settings.GetInt(taiga::kApp_Position_H)); + + if (rcWindow.left < 0 || rcWindow.left >= rcParent.right || + rcWindow.top < 0 || rcWindow.top >= rcParent.bottom) { + flags |= SWP_NOMOVE; + } + if (rcWindow.Width() < min_w) { + rcWindow.right = rcWindow.left + min_w; + } + if (rcWindow.Height() < min_h) { + rcWindow.bottom = rcWindow.top + min_h; + } + if (rcWindow.Width() > rcParent.Width()) { + rcWindow.right = rcParent.left + rcParent.Width(); + } + if (rcWindow.Height() > rcParent.Height()) { + rcWindow.bottom = rcParent.top + rcParent.Height(); + } + if (rcWindow.Width() > 0 && rcWindow.Height() > 0 && + !Settings.GetBool(taiga::kApp_Position_Maximized) && + Settings.GetBool(taiga::kApp_Position_Remember)) { + SetPosition(nullptr, rcWindow, flags); + if (flags & SWP_NOMOVE) { + CenterOwner(); + } + } + + SetSizeMin(min_w, min_h); + SetSnapGap(10); +} + +//////////////////////////////////////////////////////////////////////////////// + +INT_PTR MainDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + // Log off / Shutdown + case WM_ENDSESSION: { + OnDestroy(); + return FALSE; + } + + // Monitor anime folders + case WM_MONITORCALLBACK: { + FolderMonitor.OnChange(*reinterpret_cast(lParam)); + return TRUE; + } + + // Show menu + case WM_TAIGA_SHOWMENU: { + toolbar_wm.ShowMenu(); + return TRUE; + } + } + + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + +BOOL MainDialog::PreTranslateMessage(MSG* pMsg) { + switch (pMsg->message) { + case WM_KEYDOWN: { + switch (pMsg->wParam) { + // Clear search text + case VK_ESCAPE: { + if (::GetFocus() == edit.GetWindowHandle()) { + edit.SetText(L""); + return TRUE; + } + break; + } + // Switch tabs + case VK_TAB: { + switch (navigation.GetCurrentPage()) { + case kSidebarItemAnimeList: + if (GetKeyState(VK_CONTROL) & 0x8000) { + if (GetKeyState(VK_SHIFT) & 0x8000) { + DlgAnimeList.GoToPreviousTab(); + } else { + DlgAnimeList.GoToNextTab(); + } + return TRUE; + } + break; + } + break; + } + // Search + case VK_RETURN: { + if (::GetFocus() == edit.GetWindowHandle()) { + std::wstring text; + edit.GetText(text); + if (text.empty()) break; + switch (search_bar.mode) { + case kSearchModeService: { + ExecuteAction(L"SearchAnime(" + text + L")"); + return TRUE; + } + case kSearchModeFeed: { + DlgTorrent.Search(Settings[taiga::kTorrent_Discovery_SearchUrl], text); + return TRUE; + } + } + } + break; + } + // Focus search box + case 'F': { + if (GetKeyState(VK_CONTROL) & 0x8000) { + edit.SetFocus(); + edit.SetSel(0, -1); + return TRUE; + } + break; + } + case VK_F3: { + edit.SetFocus(); + edit.SetSel(0, -1); + return TRUE; + } + case VK_F5: { + switch (navigation.GetCurrentPage()) { + case kSidebarItemAnimeList: + // Scan available episodes + ScanAvailableEpisodes(false); + return TRUE; + case kSidebarItemHistory: + // Refresh history + DlgHistory.RefreshList(); + treeview.RefreshHistoryCounter(); + return TRUE; + case kSidebarItemStats: + // Refresh stats + Stats.CalculateAll(); + DlgStats.Refresh(); + return TRUE; + case kSidebarItemSeasons: + // Refresh season data + DlgSeason.RefreshData(); + return TRUE; + case kSidebarItemFeeds: { + // Check new torrents + Feed* feed = Aggregator.Get(kFeedCategoryLink); + if (feed) { + edit.SetText(L""); + feed->Check(Settings[taiga::kTorrent_Discovery_Source]); + return TRUE; + } + break; + } + } + break; + } + } + break; + } + + // Forward mouse wheel messages to the active page + case WM_MOUSEWHEEL: { + // Ignoring the low-order word of wParam to avoid falling into an infinite + // message-forwarding loop + pMsg->wParam = MAKEWPARAM(0, HIWORD(pMsg->wParam)); + switch (navigation.GetCurrentPage()) { + case kSidebarItemAnimeList: + return DlgAnimeList.SendMessage( + pMsg->message, pMsg->wParam, pMsg->lParam); + case kSidebarItemHistory: + return DlgHistory.SendMessage( + pMsg->message, pMsg->wParam, pMsg->lParam); + case kSidebarItemStats: + return DlgStats.SendMessage( + pMsg->message, pMsg->wParam, pMsg->lParam); + case kSidebarItemSearch: + return DlgSearch.SendMessage( + pMsg->message, pMsg->wParam, pMsg->lParam); + case kSidebarItemSeasons: + return DlgSeason.SendMessage( + pMsg->message, pMsg->wParam, pMsg->lParam); + case kSidebarItemFeeds: + return DlgTorrent.SendMessage( + pMsg->message, pMsg->wParam, pMsg->lParam); + } + break; + } + + // Back & forward buttons are used for navigation + case WM_XBUTTONUP: { + switch (HIWORD(pMsg->wParam)) { + case XBUTTON1: + navigation.GoBack(); + break; + case XBUTTON2: + navigation.GoForward(); + break; + } + return TRUE; + } + } + + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL MainDialog::OnClose() { + if (Settings.GetBool(taiga::kApp_Behavior_CloseToTray)) { + Hide(); + return TRUE; + } + + return FALSE; +} + +BOOL MainDialog::OnDestroy() { + if (Settings.GetBool(taiga::kApp_Position_Remember)) { + Settings.Set(taiga::kApp_Position_Maximized, (GetWindowLong() & WS_MAXIMIZE) ? true : false); + if (!Settings.GetBool(taiga::kApp_Position_Maximized)) { + bool invisible = !IsVisible(); + if (invisible) ActivateWindow(GetWindowHandle()); + win::Rect rcWindow; GetWindowRect(&rcWindow); + if (invisible) Hide(); + Settings.Set(taiga::kApp_Position_X, rcWindow.left); + Settings.Set(taiga::kApp_Position_Y, rcWindow.top); + Settings.Set(taiga::kApp_Position_W, rcWindow.Width()); + Settings.Set(taiga::kApp_Position_H, rcWindow.Height()); + } + } + + ui::DestroyDialog(ui::kDialogAbout); + ui::DestroyDialog(ui::kDialogAnimeInformation); + ui::DestroyDialog(ui::kDialogTestRecognition); + ui::DestroyDialog(ui::kDialogSettings); + ui::DestroyDialog(ui::kDialogUpdate); + + Taiga.Uninitialize(); + + return TRUE; +} + +void MainDialog::OnDropFiles(HDROP hDropInfo) { +#ifdef _DEBUG + WCHAR buffer[MAX_PATH]; + if (DragQueryFile(hDropInfo, 0, buffer, MAX_PATH) > 0) { + anime::Episode episode; + Meow.ExamineTitle(buffer, episode); + MessageBox(ReplaceVariables(Settings[taiga::kSync_Notify_Format], episode).c_str(), TAIGA_APP_TITLE, MB_OK); + } +#endif +} + +LRESULT MainDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { + // Toolbar controls + if (idCtrl == IDC_TOOLBAR_MENU || + idCtrl == IDC_TOOLBAR_MAIN || + idCtrl == IDC_TOOLBAR_SEARCH) { + return OnToolbarNotify(reinterpret_cast(pnmh)); + + // Tree control + } else if (idCtrl == IDC_TREE_MAIN) { + return OnTreeNotify(reinterpret_cast(pnmh)); + + // Statusbar control + } else if (idCtrl == IDC_STATUSBAR_MAIN) { + return OnStatusbarNotify(reinterpret_cast(pnmh)); + + // Button control + } else if (idCtrl == IDC_BUTTON_CANCELSEARCH) { + if (pnmh->code == NM_CUSTOMDRAW) { + return cancel_button.OnCustomDraw(reinterpret_cast(pnmh)); + } + } + + return 0; +} + +void MainDialog::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { + // Paint sidebar + if (treeview.IsVisible()) { + win::Dc dc = hdc; + win::Rect rect; + + rect.Copy(rect_sidebar_); + dc.FillRect(rect, ::GetSysColor(COLOR_3DFACE)); + + rect.left = rect.right - 1; + dc.FillRect(rect, ::GetSysColor(COLOR_ACTIVEBORDER)); + } +} + +void MainDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { + switch (uMsg) { + case WM_ENTERSIZEMOVE: { + if (::IsAppThemed() && win::GetVersion() >= win::kVersionVista) { + SetTransparency(200); + } + break; + } + case WM_EXITSIZEMOVE: { + if (::IsAppThemed() && win::GetVersion() >= win::kVersionVista) { + SetTransparency(255); + } + break; + } + case WM_SIZE: { + if (IsIconic()) { + if (Settings.GetBool(taiga::kApp_Behavior_MinimizeToTray)) + Hide(); + return; + } + UpdateControlPositions(&size); + break; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +/* Taskbar */ + +void MainDialog::OnTaskbarCallback(UINT uMsg, LPARAM lParam) { + // Taskbar creation notification + if (uMsg == WM_TASKBARCREATED) { + Taskbar.Create(GetWindowHandle(), nullptr, TAIGA_APP_TITLE); + + // Windows 7 taskbar interface + } else if (uMsg == WM_TASKBARBUTTONCREATED) { + TaskbarList.Initialize(GetWindowHandle()); + + // Taskbar callback + } else if (uMsg == WM_TASKBARCALLBACK) { + switch (lParam) { + case NIN_BALLOONSHOW: { + break; + } + case NIN_BALLOONTIMEOUT: { + Taiga.current_tip_type = taiga::kTipTypeDefault; + break; + } + case NIN_BALLOONUSERCLICK: { + switch (Taiga.current_tip_type) { + case taiga::kTipTypeNowPlaying: + navigation.SetCurrentPage(kSidebarItemNowPlaying); + break; + case taiga::kTipTypeSearch: + ExecuteAction(L"SearchAnime(" + CurrentEpisode.title + L")"); + break; + case taiga::kTipTypeTorrent: + navigation.SetCurrentPage(kSidebarItemFeeds); + break; + case taiga::kTipTypeUpdateFailed: + History.queue.Check(false); + break; + } + ActivateWindow(GetWindowHandle()); + Taiga.current_tip_type = taiga::kTipTypeDefault; + break; + } + case WM_LBUTTONUP: + case WM_LBUTTONDBLCLK: { + ActivateWindow(GetWindowHandle()); + break; + } + case WM_RBUTTONUP: { + ui::Menus.UpdateAll(DlgAnimeList.GetCurrentItem()); + SetForegroundWindow(); + ExecuteAction(ui::Menus.Show(GetWindowHandle(), 0, 0, L"Tray")); + ui::Menus.UpdateAll(DlgAnimeList.GetCurrentItem()); + break; + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void MainDialog::ChangeStatus(std::wstring str) { + if (!str.empty()) str = L" " + str; + statusbar.SetText(str.c_str()); +} + +void MainDialog::EnableInput(bool enable) { + // Toolbar buttons + toolbar_main.EnableButton(kToolbarButtonSync, enable); + // Content + DlgAnimeList.Enable(enable); + DlgHistory.Enable(enable); +} + +void MainDialog::UpdateControlPositions(const SIZE* size) { + // Set client area + win::Rect rect_client; + if (size == nullptr) { + GetClientRect(&rect_client); + } else { + rect_client.Set(0, 0, size->cx, size->cy); + } + + // Resize rebar + rebar.SendMessage(WM_SIZE, 0, 0); + rect_client.top += rebar.GetBarHeight(); + + // Resize status bar + win::Rect rcStatus; + statusbar.GetClientRect(&rcStatus); + statusbar.SendMessage(WM_SIZE, 0, 0); + UpdateStatusTimer(); + rect_client.bottom -= rcStatus.Height(); + + // Set sidebar + rect_sidebar_.Set(0, rect_client.top, 140, rect_client.bottom); + // Resize treeview + if (treeview.IsVisible()) { + win::Rect rect_tree(rect_sidebar_); + rect_tree.Inflate(-ScaleX(win::kControlMargin), -ScaleY(win::kControlMargin)); + treeview.SetPosition(nullptr, rect_tree); + } + + // Set content + if (treeview.IsVisible()) { + rect_content_.Subtract(rect_client, rect_sidebar_); + } else { + rect_content_ = rect_client; + } + + // Resize content + DlgAnimeList.SetPosition(nullptr, rect_content_); + DlgHistory.SetPosition(nullptr, rect_content_); + DlgNowPlaying.SetPosition(nullptr, rect_content_); + DlgSearch.SetPosition(nullptr, rect_content_); + DlgSeason.SetPosition(nullptr, rect_content_); + DlgStats.SetPosition(nullptr, rect_content_); + DlgTorrent.SetPosition(nullptr, rect_content_); +} + +void MainDialog::UpdateStatusTimer() { + win::Rect rect; + GetClientRect(&rect); + + auto timer = taiga::timers.timer(taiga::kTimerMedia); + int seconds = timer ? timer->ticks() : 0; + + if (seconds > 0 && + CurrentEpisode.anime_id > anime::ID_UNKNOWN && + IsUpdateAllowed(*AnimeDatabase.FindItem(CurrentEpisode.anime_id), + CurrentEpisode, true)) { + std::wstring str = L"List update in " + ToTimeString(seconds); + statusbar.SetPartText(1, str.c_str()); + statusbar.SetPartTipText(1, str.c_str()); + + statusbar.SetPartWidth(0, rect.Width() - ScaleX(160)); + statusbar.SetPartWidth(1, ScaleX(160)); + + } else { + statusbar.SetPartWidth(0, rect.Width()); + statusbar.SetPartWidth(1, 0); + } +} + +void MainDialog::UpdateTip() { + std::wstring tip = TAIGA_APP_TITLE; + if (Taiga.debug_mode) + tip += L" [debug]"; + + if (CurrentEpisode.anime_id > anime::ID_UNKNOWN) { + auto anime_item = AnimeDatabase.FindItem(CurrentEpisode.anime_id); + tip += L"\nWatching: " + anime_item->GetTitle() + + (!CurrentEpisode.number.empty() ? L" #" + CurrentEpisode.number : L""); + } + + Taskbar.Modify(tip.c_str()); +} + +void MainDialog::UpdateTitle() { + std::wstring title = TAIGA_APP_TITLE; + if (Taiga.debug_mode) + title += L" [debug]"; + + const std::wstring username = taiga::GetCurrentUsername(); + if (!username.empty()) + title += L" \u2013 " + username; + if (Taiga.debug_mode) { + auto service = taiga::GetCurrentService(); + if (service) + title += L" @ " + service->name(); + } + + if (CurrentEpisode.anime_id > anime::ID_UNKNOWN) { + auto anime_item = AnimeDatabase.FindItem(CurrentEpisode.anime_id); + title += L" \u2013 " + anime_item->GetTitle() + PushString(L" #", CurrentEpisode.number); + if (Settings.GetBool(taiga::kSync_Update_OutOfRange) && + anime::GetEpisodeLow(CurrentEpisode.number) > anime_item->GetMyLastWatchedEpisode() + 1) { + title += L" (out of range)"; + } + } + + SetText(title); +} + +//////////////////////////////////////////////////////////////////////////////// + +int MainDialog::Navigation::GetCurrentPage() { + return current_page_; +} + +void MainDialog::Navigation::SetCurrentPage(int page, bool add_to_history) { + if (page == current_page_) + return; + + int previous_page = current_page_; + current_page_ = page; + + RefreshSearchText(previous_page); + + #define DISPLAY_PAGE(item, dialog, resource_id) \ + case item: \ + if (!dialog.IsWindow()) dialog.Create(resource_id, parent->GetWindowHandle(), false); \ + parent->UpdateControlPositions(); \ + dialog.Show(); \ + break; + switch (current_page_) { + DISPLAY_PAGE(kSidebarItemNowPlaying, DlgNowPlaying, IDD_ANIME_INFO); + DISPLAY_PAGE(kSidebarItemAnimeList, DlgAnimeList, IDD_ANIME_LIST); + DISPLAY_PAGE(kSidebarItemHistory, DlgHistory, IDD_HISTORY); + DISPLAY_PAGE(kSidebarItemStats, DlgStats, IDD_STATS); + DISPLAY_PAGE(kSidebarItemSearch, DlgSearch, IDD_SEARCH); + DISPLAY_PAGE(kSidebarItemSeasons, DlgSeason, IDD_SEASON); + DISPLAY_PAGE(kSidebarItemFeeds, DlgTorrent, IDD_TORRENT); + } + #undef DISPLAY_PAGE + + if (current_page_ != kSidebarItemNowPlaying) DlgNowPlaying.Hide(); + if (current_page_ != kSidebarItemAnimeList) DlgAnimeList.Hide(); + if (current_page_ != kSidebarItemHistory) DlgHistory.Hide(); + if (current_page_ != kSidebarItemStats) DlgStats.Hide(); + if (current_page_ != kSidebarItemSearch) DlgSearch.Hide(); + if (current_page_ != kSidebarItemSeasons) DlgSeason.Hide(); + if (current_page_ != kSidebarItemFeeds) DlgTorrent.Hide(); + + parent->treeview.SelectItem(parent->treeview.hti.at(current_page_)); + + ui::Menus.UpdateView(); + Refresh(add_to_history); +} + +void MainDialog::Navigation::GoBack() { + if (index_ > 0) { + index_--; + SetCurrentPage(items_.at(index_), false); + } +} + +void MainDialog::Navigation::GoForward() { + if (index_ < static_cast(items_.size()) - 1) { + index_++; + SetCurrentPage(items_.at(index_), false); + } +} + +void MainDialog::Navigation::Refresh(bool add_to_history) { + if (add_to_history) { + auto it = std::find(items_.begin(), items_.end(), current_page_); + if (it != items_.end()) + items_.erase(it); + + items_.push_back(current_page_); + index_ = items_.size() - 1; + } +} + +void MainDialog::Navigation::RefreshSearchText(int previous_page) { + std::wstring cue_text; + std::wstring search_text; + + switch (current_page_) { + case kSidebarItemAnimeList: + case kSidebarItemSeasons: + parent->search_bar.mode = kSearchModeService; + cue_text = L"Filter list or search " + taiga::GetCurrentService()->name(); + break; + case kSidebarItemNowPlaying: + case kSidebarItemHistory: + case kSidebarItemStats: + case kSidebarItemSearch: + parent->search_bar.mode = kSearchModeService; + cue_text = L"Search " + taiga::GetCurrentService()->name() + L" for anime"; + if (current_page_ == kSidebarItemSearch) + search_text = DlgSearch.search_text; + break; + case kSidebarItemFeeds: + parent->search_bar.mode = kSearchModeFeed; + cue_text = L"Search for torrents"; + break; + } + + if (!parent->search_bar.filters.text.empty()) { + parent->search_bar.filters.text.clear(); + switch (previous_page) { + case kSidebarItemAnimeList: + DlgAnimeList.RefreshList(); + DlgAnimeList.RefreshTabs(); + break; + case kSidebarItemSeasons: + DlgSeason.RefreshList(); + break; + } + } + + parent->edit.SetCueBannerText(cue_text.c_str()); + parent->edit.SetText(search_text); +} + +} // namespace ui \ No newline at end of file diff --git a/dlg/dlg_main.h b/src/ui/dlg/dlg_main.h similarity index 62% rename from dlg/dlg_main.h rename to src/ui/dlg/dlg_main.h index 5f778ac10..907f41716 100644 --- a/dlg/dlg_main.h +++ b/src/ui/dlg/dlg_main.h @@ -1,158 +1,162 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_MAIN_H -#define DLG_MAIN_H - -#include "../std.h" - -#include "../anime_filter.h" - -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" -#include "../win32/win_gdi.h" - -#define WM_TAIGA_SHOWMENU WM_USER + 1337 - -enum MainToolbarButtons { - TOOLBAR_BUTTON_SYNCHRONIZE = 200, - TOOLBAR_BUTTON_MAL = 201, - TOOLBAR_BUTTON_FOLDERS = 203, - TOOLBAR_BUTTON_TOOLS = 204, - TOOLBAR_BUTTON_SETTINGS = 206, - TOOLBAR_BUTTON_ABOUT = 208 -}; - -enum SearchMode { - SEARCH_MODE_NONE, - SEARCH_MODE_MAL, - SEARCH_MODE_FEED -}; - -enum SidebarItems { - SIDEBAR_ITEM_NOWPLAYING = 0, - SIDEBAR_ITEM_ANIMELIST = 2, - SIDEBAR_ITEM_HISTORY = 3, - SIDEBAR_ITEM_STATS = 4, - SIDEBAR_ITEM_SEARCH = 6, - SIDEBAR_ITEM_SEASONS = 7, - SIDEBAR_ITEM_FEEDS = 8 -}; - -// ============================================================================= - -class MainDialog : public win32::Dialog { -public: - MainDialog(); - virtual ~MainDialog() {} - - INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - BOOL OnClose(); - BOOL OnCommand(WPARAM wParam, LPARAM lParam); - BOOL OnDestroy(); - void OnDropFiles(HDROP hDropInfo); - BOOL OnInitDialog(); - LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); - void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); - void OnSize(UINT uMsg, UINT nType, SIZE size); - void OnTaskbarCallback(UINT uMsg, LPARAM lParam); - void OnTimer(UINT_PTR nIDEvent); - LRESULT OnToolbarNotify(LPARAM lParam); - LRESULT OnTreeNotify(LPARAM lParam); - BOOL PreTranslateMessage(MSG* pMsg); - -public: - void ChangeStatus(wstring str = L""); - void EnableInput(bool enable = true); - void UpdateControlPositions(const SIZE* size = nullptr); - void UpdateStatusTimer(); - void UpdateTip(); - void UpdateTitle(); - - class Navigation { - public: - Navigation() : current_page_(-1), index_(-1) {} - int GetCurrentPage(); - void SetCurrentPage(int page, bool reorder = true); - void GoBack(); - void GoForward(); - void Refresh(bool add_to_history); - MainDialog* parent; - private: - int current_page_; - int index_; - vector items_; - } navigation; - -private: - void CreateDialogControls(); - void InitWindowPosition(); - - class ToolbarWithMenu { - public: - ToolbarWithMenu() : button_index(-1), hook(nullptr), toolbar(nullptr) {} - static LRESULT CALLBACK HookProc(int code, WPARAM wParam, LPARAM lParam); - void ShowMenu(); - int button_index; - HHOOK hook; - win32::Toolbar* toolbar; - } toolbar_wm; - -public: - // TreeView control - class MainTree : public win32::TreeView { - public: - LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - BOOL IsVisible(); - void RefreshHistoryCounter(); - vector hti; - } treeview; - - // Edit control - class EditSearch : public win32::Edit { - LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - } edit; - - // Cancel button - class CancelButton : public win32::Window { - public: - LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - LRESULT OnCustomDraw(LPARAM lParam); - } cancel_button; - - // Other controls - win32::Rebar rebar; - win32::StatusBar statusbar; - win32::Toolbar toolbar_menu, toolbar_main, toolbar_search; - - // Search bar - class SearchBar { - public: - SearchBar() : mode(SEARCH_MODE_NONE) {} - int mode; - MainDialog* parent; - anime::Filters filters; - } search_bar; - -private: - win32::Rect rect_content_, rect_sidebar_; -}; - -extern class MainDialog MainDialog; - -#endif // DLG_MAIN_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_MAIN_H +#define TAIGA_UI_DLG_MAIN_H + +#include "library/anime_filter.h" +#include "win/ctrl/win_ctrl.h" +#include "win/win_dialog.h" +#include "win/win_gdi.h" + +#define WM_TAIGA_SHOWMENU WM_USER + 1337 + +namespace ui { + +enum MainToolbarButtons { + kToolbarButtonSync = 200, + kToolbarButtonFolders = 202, + kToolbarButtonTools = 203, + kToolbarButtonSettings = 205, + kToolbarButtonDebug = 207 +}; + +enum SearchMode { + kSearchModeNone, + kSearchModeService, + kSearchModeFeed +}; + +enum SidebarItems { + kSidebarItemNowPlaying = 0, + kSidebarItemAnimeList = 2, + kSidebarItemHistory = 3, + kSidebarItemStats = 4, + kSidebarItemSearch = 6, + kSidebarItemSeasons = 7, + kSidebarItemFeeds = 8 +}; + +class MainDialog : public win::Dialog { +public: + MainDialog(); + virtual ~MainDialog() {} + + INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + BOOL OnClose(); + BOOL OnCommand(WPARAM wParam, LPARAM lParam); + BOOL OnDestroy(); + void OnDropFiles(HDROP hDropInfo); + BOOL OnInitDialog(); + LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); + void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); + void OnSize(UINT uMsg, UINT nType, SIZE size); + void OnTaskbarCallback(UINT uMsg, LPARAM lParam); + LRESULT OnStatusbarNotify(LPARAM lParam); + LRESULT OnToolbarNotify(LPARAM lParam); + LRESULT OnTreeNotify(LPARAM lParam); + BOOL PreTranslateMessage(MSG* pMsg); + +public: + void ChangeStatus(std::wstring str = L""); + void EnableInput(bool enable = true); + void UpdateControlPositions(const SIZE* size = nullptr); + void UpdateStatusTimer(); + void UpdateTip(); + void UpdateTitle(); + + class Navigation { + public: + Navigation() : current_page_(-1), index_(-1), parent(nullptr) {} + int GetCurrentPage(); + void SetCurrentPage(int page, bool add_to_history = true); + void GoBack(); + void GoForward(); + void Refresh(bool add_to_history); + void RefreshSearchText(int previous_page); + MainDialog* parent; + private: + int current_page_; + int index_; + std::vector items_; + } navigation; + +private: + void CreateDialogControls(); + void InitWindowPosition(); + + class ToolbarWithMenu { + public: + ToolbarWithMenu() : button_index(-1), hook(nullptr), toolbar(nullptr) {} + static LRESULT CALLBACK HookProc(int code, WPARAM wParam, LPARAM lParam); + void ShowMenu(); + int button_index; + HHOOK hook; + win::Toolbar* toolbar; + } toolbar_wm; + +public: + // TreeView control + class MainTree : public win::TreeView { + public: + LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + BOOL IsVisible(); + void RefreshHistoryCounter(); + std::vector hti; + } treeview; + + // Edit control + class EditSearch : public win::Edit { + LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + } edit; + + // Cancel button + class CancelButton : public win::Window { + public: + LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + LRESULT OnCustomDraw(LPARAM lParam); + } cancel_button; + + // Statusbar + class StatusBar : public win::StatusBar { + public: + LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + } statusbar; + + // Other controls + win::Rebar rebar; + win::Toolbar toolbar_menu, toolbar_main, toolbar_search; + + // Search bar + class SearchBar { + public: + SearchBar() : mode(kSearchModeNone), parent(nullptr) {} + int mode; + MainDialog* parent; + anime::Filters filters; + } search_bar; + +private: + win::Rect rect_content_, rect_sidebar_; +}; + +extern MainDialog DlgMain; + +} // namespace ui + +#endif // TAIGA_UI_DLG_MAIN_H \ No newline at end of file diff --git a/dlg/dlg_main_controls.cpp b/src/ui/dlg/dlg_main_controls.cpp similarity index 71% rename from dlg/dlg_main_controls.cpp rename to src/ui/dlg/dlg_main_controls.cpp index 4622663b7..0e8847a90 100644 --- a/dlg/dlg_main_controls.cpp +++ b/src/ui/dlg/dlg_main_controls.cpp @@ -1,372 +1,406 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_main.h" - -#include "dlg_anime_list.h" -#include "dlg_history.h" -#include "dlg_season.h" -#include "dlg_stats.h" - -#include "../anime_db.h" -#include "../anime_filter.h" -#include "../common.h" -#include "../debug.h" -#include "../gfx.h" -#include "../history.h" -#include "../myanimelist.h" -#include "../resource.h" -#include "../settings.h" -#include "../string.h" -#include "../taiga.h" -#include "../theme.h" - -#include "../win32/win_gdi.h" - -// ============================================================================= - -/* TreeView control */ - -LRESULT MainDialog::MainTree::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_SETCURSOR: { - TVHITTESTINFO ht = {0}; - HitTest(&ht, true); - int index = GetItemData(ht.hItem); - if (index == -1) { - SetSharedCursor(IDC_ARROW); - return TRUE; - } - break; - } - } - - return WindowProcDefault(hwnd, uMsg, wParam, lParam); -} - -void MainDialog::MainTree::RefreshHistoryCounter() { - wstring text = L"History"; - int count = History.queue.GetItemCount(); - if (count > 0) text += L" (" + ToWstr(count) + L")"; - SetItem(hti.at(SIDEBAR_ITEM_HISTORY), text.c_str()); -} - -BOOL MainDialog::MainTree::IsVisible() { - // This hack ensures that the sidebar is considered visible on startup - if (!::MainDialog.IsVisible()) return TRUE; - return TreeView::IsVisible(); -} - -LRESULT MainDialog::OnTreeNotify(LPARAM lParam) { - LPNMHDR pnmh = reinterpret_cast(lParam); - - switch (pnmh->code) { - // Custom draw - case NM_CUSTOMDRAW: { - LPNMTVCUSTOMDRAW pCD = reinterpret_cast(lParam); - switch (pCD->nmcd.dwDrawStage) { - case CDDS_PREPAINT: - return CDRF_NOTIFYITEMDRAW; - case CDDS_ITEMPREPAINT: - return CDRF_NOTIFYPOSTPAINT; - case CDDS_ITEMPOSTPAINT: { - // Draw separator - if (pCD->nmcd.lItemlParam == -1) { - win32::Rect rcItem = pCD->nmcd.rc; - win32::Dc hdc = pCD->nmcd.hdc; - hdc.FillRect(rcItem, ::GetSysColor(COLOR_3DFACE)); - rcItem.top += (rcItem.bottom - rcItem.top) / 2; - //GradientRect(hdc.Get(), &rcItem, ::GetSysColor(COLOR_3DLIGHT), ::GetSysColor(COLOR_3DFACE), true); - rcItem.bottom = rcItem.top + 2; - hdc.FillRect(rcItem, ::GetSysColor(COLOR_3DHIGHLIGHT)); - rcItem.bottom -= 1; - hdc.FillRect(rcItem, ::GetSysColor(COLOR_3DLIGHT)); - hdc.DetachDC(); - } - return CDRF_DODEFAULT; - } - } - break; - } - // Item select - case TVN_SELCHANGING: { - LPNMTREEVIEW pnmtv = reinterpret_cast(lParam); - switch (pnmtv->action) { - case TVC_UNKNOWN: - break; - case TVC_BYMOUSE: - case TVC_BYKEYBOARD: - // Prevent selection of separators - if (pnmtv->itemNew.lParam == -1) { - if (pnmtv->action == TVC_BYKEYBOARD) { - // TODO: Should work upwards too - HTREEITEM hti = TreeView_GetNextItem(treeview.GetWindowHandle(), - pnmtv->itemNew.hItem, TVGN_NEXT); - navigation.SetCurrentPage(treeview.GetItemData(hti)); - } - return TRUE; - } - navigation.SetCurrentPage(pnmtv->itemNew.lParam); - break; - } - break; - } - } - - return 0; -} - -// ============================================================================= - -/* Button control */ - -LRESULT MainDialog::CancelButton::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_SETCURSOR: { - SetSharedCursor(IDC_HAND); - return TRUE; - } - } - - return WindowProcDefault(hwnd, uMsg, wParam, lParam); -} - -LRESULT MainDialog::CancelButton::OnCustomDraw(LPARAM lParam) { - LPNMCUSTOMDRAW pCD = reinterpret_cast(lParam); - - switch (pCD->dwDrawStage) { - case CDDS_PREPAINT: { - win32::Dc dc = pCD->hdc; - dc.FillRect(pCD->rc, ::GetSysColor(COLOR_WINDOW)); - UI.ImgList16.Draw(ICON16_CROSS, dc.Get(), 0, 0); - dc.DetachDC(); - return CDRF_SKIPDEFAULT; - } - } - - return 0; -} - -/* Edit control */ - -LRESULT MainDialog::EditSearch::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_COMMAND: { - if (HIWORD(wParam) == BN_CLICKED) { - // Clear search text - if (LOWORD(wParam) == IDC_BUTTON_CANCELSEARCH) { - SetText(L""); - return TRUE; - } - } - break; - } - } - - return WindowProcDefault(hwnd, uMsg, wParam, lParam); -} - -// ============================================================================= - -/* Toolbar */ - -BOOL MainDialog::OnCommand(WPARAM wParam, LPARAM lParam) { - // Toolbar - switch (LOWORD(wParam)) { - // Synchronize - case TOOLBAR_BUTTON_SYNCHRONIZE: - ExecuteAction(L"Synchronize"); - return TRUE; - // MyAnimeList - case TOOLBAR_BUTTON_MAL: - ExecuteAction(L"ViewPanel"); - return TRUE; - // Settings - case TOOLBAR_BUTTON_SETTINGS: - ExecuteAction(L"Settings"); - return TRUE; - // Debug - case TOOLBAR_BUTTON_ABOUT: - debug::Test(); - return TRUE; - } - - // Search text - if (HIWORD(wParam) == EN_CHANGE) { - if (LOWORD(wParam) == IDC_EDIT_SEARCH) { - wstring text; - edit.GetText(text); - cancel_button.Show(text.empty() ? SW_HIDE : SW_SHOWNORMAL); - switch (navigation.GetCurrentPage()) { - case SIDEBAR_ITEM_ANIMELIST: - if (search_bar.filters.text != text) { - search_bar.filters.text = text; - AnimeListDialog.RefreshList(); - AnimeListDialog.RefreshTabs(); - return TRUE; - } - break; - case SIDEBAR_ITEM_SEASONS: - if (search_bar.filters.text != text) { - search_bar.filters.text = text; - SeasonDialog.RefreshList(); - return TRUE; - } - break; - } - } - } - - return FALSE; -} - -LRESULT CALLBACK MainDialog::ToolbarWithMenu::HookProc(int code, WPARAM wParam, LPARAM lParam) { - if (code == MSGF_MENU) { - MSG* msg = reinterpret_cast(lParam); - - switch (msg->message) { - case WM_LBUTTONDOWN: - case WM_RBUTTONDOWN: { - break; - } - - case WM_MOUSEMOVE: { - POINT pt = {LOWORD(msg->lParam), HIWORD(msg->lParam)}; - ScreenToClient(::MainDialog.toolbar_wm.toolbar->GetWindowHandle(), &pt); - - int button_index = ::MainDialog.toolbar_wm.toolbar->HitTest(pt); - int button_count = ::MainDialog.toolbar_wm.toolbar->GetButtonCount(); - DWORD button_style = ::MainDialog.toolbar_wm.toolbar->GetButtonStyle(button_index); - - if (button_index > -1 && - button_index < button_count && - button_index != ::MainDialog.toolbar_wm.button_index) { - if (button_style & BTNS_DROPDOWN || button_style & BTNS_WHOLEDROPDOWN) { - ::MainDialog.toolbar_wm.toolbar->SendMessage(TB_SETHOTITEM, button_index, 0); - return 0L; - } - } - - break; - } - } - } - - return CallNextHookEx(::MainDialog.toolbar_wm.hook, code, wParam, lParam); -} - -LRESULT MainDialog::OnToolbarNotify(LPARAM lParam) { - switch (reinterpret_cast(lParam)->code) { - // Dropdown button click - case TBN_DROPDOWN: { - LPNMTOOLBAR nmt = reinterpret_cast(lParam); - int toolbar_id = nmt->hdr.idFrom; - switch (toolbar_id) { - case IDC_TOOLBAR_MENU: - toolbar_wm.toolbar = &toolbar_menu; - break; - case IDC_TOOLBAR_MAIN: - toolbar_wm.toolbar = &toolbar_main; - break; - case IDC_TOOLBAR_SEARCH: - toolbar_wm.toolbar = &toolbar_search; - break; - } - toolbar_wm.ShowMenu(); - break; - } - - // Show tooltips - case TBN_GETINFOTIP: { - NMTBGETINFOTIP* git = reinterpret_cast(lParam); - git->cchTextMax = INFOTIPSIZE; - // Main toolbar - if (git->hdr.hwndFrom == toolbar_main.GetWindowHandle()) { - git->pszText = (LPWSTR)(toolbar_main.GetButtonTooltip(git->lParam)); - // Search toolbar - } else if (git->hdr.hwndFrom == toolbar_search.GetWindowHandle()) { - git->pszText = (LPWSTR)(toolbar_search.GetButtonTooltip(git->lParam)); - } - break; - } - - // Hot-tracking - case TBN_HOTITEMCHANGE: { - LPNMTBHOTITEM lpnmhi = reinterpret_cast(lParam); - if (toolbar_wm.hook && lpnmhi->idNew > 0) { - debug::Print(L"Old: " + ToWstr(lpnmhi->idOld) + L" | New: " + ToWstr(lpnmhi->idNew) + L"\n"); - toolbar_wm.toolbar->PressButton(lpnmhi->idOld, FALSE); - toolbar_wm.toolbar->SendMessage(lpnmhi->idNew, TRUE); - SendMessage(WM_CANCELMODE); - PostMessage(WM_TAIGA_SHOWMENU); - } - break; - } - } - - return 0L; -} - -void MainDialog::ToolbarWithMenu::ShowMenu() { - button_index = toolbar->SendMessage(TB_GETHOTITEM); - - TBBUTTON tbb; - toolbar->GetButton(button_index, tbb); - toolbar->PressButton(tbb.idCommand, TRUE); - - // Calculate point - RECT rect; - toolbar->SendMessage(TB_GETITEMRECT, button_index, reinterpret_cast(&rect)); - POINT pt = {rect.left, rect.bottom}; - ClientToScreen(toolbar->GetWindowHandle(), &pt); - - // Hook - if (hook) UnhookWindowsHookEx(hook); - hook = SetWindowsHookEx(WH_MSGFILTER, &HookProc, NULL, GetCurrentThreadId()); - - // Display menu - wstring action; - HWND hwnd = ::MainDialog.GetWindowHandle(); - #define SHOWUIMENU(id, name) \ - case id: action = UI.Menus.Show(hwnd, pt.x, pt.y, name); break; - switch (tbb.idCommand) { - SHOWUIMENU(100, L"File"); - SHOWUIMENU(101, L"MyAnimeList"); - SHOWUIMENU(102, L"Tools"); - SHOWUIMENU(103, L"View"); - SHOWUIMENU(104, L"Help"); - SHOWUIMENU(TOOLBAR_BUTTON_FOLDERS, L"Folders"); - SHOWUIMENU(TOOLBAR_BUTTON_TOOLS, L"ExternalLinks"); - } - #undef SHOWUIMENU - - // Unhook - if (hook) { - UnhookWindowsHookEx(hook); - hook = nullptr; - } - - toolbar->PressButton(tbb.idCommand, FALSE); - - if (!action.empty()) { - ExecuteAction(action); - UpdateAllMenus(AnimeListDialog.GetCurrentItem()); - } -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/string.h" +#include "library/history.h" +#include "sync/sync.h" +#include "taiga/debug.h" +#include "taiga/resource.h" +#include "taiga/script.h" +#include "ui/dlg/dlg_anime_list.h" +#include "ui/dlg/dlg_main.h" +#include "ui/dlg/dlg_season.h" +#include "ui/dialog.h" +#include "ui/menu.h" +#include "ui/theme.h" +#include "ui/ui.h" + +namespace ui { + +//////////////////////////////////////////////////////////////////////////////// + +/* TreeView control */ + +LRESULT MainDialog::MainTree::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_SETCURSOR: { + TVHITTESTINFO ht = {0}; + HitTest(&ht, true); + int index = GetItemData(ht.hItem); + if (index == -1) { + SetSharedCursor(IDC_ARROW); + return TRUE; + } + break; + } + } + + return WindowProcDefault(hwnd, uMsg, wParam, lParam); +} + +void MainDialog::MainTree::RefreshHistoryCounter() { + std::wstring text = L"History"; + int count = History.queue.GetItemCount(); + if (count > 0) text += L" (" + ToWstr(count) + L")"; + SetItem(hti.at(kSidebarItemHistory), text.c_str()); +} + +BOOL MainDialog::MainTree::IsVisible() { + // This hack ensures that the sidebar is considered visible on startup + if (!DlgMain.IsVisible()) + return TRUE; + + return TreeView::IsVisible(); +} + +LRESULT MainDialog::OnTreeNotify(LPARAM lParam) { + LPNMHDR pnmh = reinterpret_cast(lParam); + + switch (pnmh->code) { + // Custom draw + case NM_CUSTOMDRAW: { + LPNMTVCUSTOMDRAW pCD = reinterpret_cast(lParam); + switch (pCD->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + return CDRF_NOTIFYITEMDRAW; + case CDDS_ITEMPREPAINT: + return CDRF_NOTIFYPOSTPAINT; + case CDDS_ITEMPOSTPAINT: { + // Draw separator + if (pCD->nmcd.lItemlParam == -1) { + win::Rect rcItem = pCD->nmcd.rc; + win::Dc hdc = pCD->nmcd.hdc; + hdc.FillRect(rcItem, ::GetSysColor(COLOR_3DFACE)); + rcItem.top += (rcItem.bottom - rcItem.top) / 2; + //GradientRect(hdc.Get(), &rcItem, ::GetSysColor(COLOR_3DLIGHT), ::GetSysColor(COLOR_3DFACE), true); + rcItem.bottom = rcItem.top + 2; + hdc.FillRect(rcItem, ::GetSysColor(COLOR_3DHIGHLIGHT)); + rcItem.bottom -= 1; + hdc.FillRect(rcItem, ::GetSysColor(COLOR_3DLIGHT)); + hdc.DetachDc(); + } + return CDRF_DODEFAULT; + } + } + break; + } + // Item select + case TVN_SELCHANGING: { + LPNMTREEVIEW pnmtv = reinterpret_cast(lParam); + switch (pnmtv->action) { + case TVC_UNKNOWN: + break; + case TVC_BYMOUSE: + case TVC_BYKEYBOARD: + // Prevent selection of separators + if (pnmtv->itemNew.lParam == -1) { + if (pnmtv->action == TVC_BYKEYBOARD) { + // TODO: Should work upwards too + HTREEITEM hti = TreeView_GetNextItem(treeview.GetWindowHandle(), + pnmtv->itemNew.hItem, TVGN_NEXT); + navigation.SetCurrentPage(treeview.GetItemData(hti)); + } + return TRUE; + } + navigation.SetCurrentPage(pnmtv->itemNew.lParam); + break; + } + break; + } + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// + +/* Button control */ + +LRESULT MainDialog::CancelButton::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_SETCURSOR: { + SetSharedCursor(IDC_HAND); + return TRUE; + } + } + + return WindowProcDefault(hwnd, uMsg, wParam, lParam); +} + +LRESULT MainDialog::CancelButton::OnCustomDraw(LPARAM lParam) { + LPNMCUSTOMDRAW pCD = reinterpret_cast(lParam); + + switch (pCD->dwDrawStage) { + case CDDS_PREPAINT: { + win::Dc dc = pCD->hdc; + dc.FillRect(pCD->rc, ::GetSysColor(COLOR_WINDOW)); + ui::Theme.GetImageList16().Draw(ui::kIcon16_Cross, dc.Get(), 0, 0); + dc.DetachDc(); + return CDRF_SKIPDEFAULT; + } + } + + return 0; +} + +/* Edit control */ + +LRESULT MainDialog::EditSearch::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_COMMAND: { + if (HIWORD(wParam) == BN_CLICKED) { + // Clear search text + if (LOWORD(wParam) == IDC_BUTTON_CANCELSEARCH) { + SetText(L""); + return TRUE; + } + } + break; + } + } + + return WindowProcDefault(hwnd, uMsg, wParam, lParam); +} + +//////////////////////////////////////////////////////////////////////////////// + +/* Toolbar */ + +BOOL MainDialog::OnCommand(WPARAM wParam, LPARAM lParam) { + // Toolbar + switch (LOWORD(wParam)) { + // Synchronize + case kToolbarButtonSync: + sync::Synchronize(); + return TRUE; + // Settings + case kToolbarButtonSettings: + ShowDialog(ui::kDialogSettings); + return TRUE; + // Debug + case kToolbarButtonDebug: + debug::Test(); + return TRUE; + } + + // Search text + if (HIWORD(wParam) == EN_CHANGE) { + if (LOWORD(wParam) == IDC_EDIT_SEARCH) { + std::wstring text; + edit.GetText(text); + cancel_button.Show(text.empty() ? SW_HIDE : SW_SHOWNORMAL); + switch (navigation.GetCurrentPage()) { + case kSidebarItemAnimeList: + if (search_bar.filters.text != text) { + search_bar.filters.text = text; + DlgAnimeList.RefreshList(); + DlgAnimeList.RefreshTabs(); + return TRUE; + } + break; + case kSidebarItemSeasons: + if (search_bar.filters.text != text) { + search_bar.filters.text = text; + DlgSeason.RefreshList(); + return TRUE; + } + break; + } + } + } + + return FALSE; +} + +LRESULT CALLBACK MainDialog::ToolbarWithMenu::HookProc(int code, WPARAM wParam, LPARAM lParam) { + if (code == MSGF_MENU) { + MSG* msg = reinterpret_cast(lParam); + + switch (msg->message) { + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: { + break; + } + + case WM_MOUSEMOVE: { + POINT pt = {LOWORD(msg->lParam), HIWORD(msg->lParam)}; + ScreenToClient(DlgMain.toolbar_wm.toolbar->GetWindowHandle(), &pt); + + int button_index = DlgMain.toolbar_wm.toolbar->HitTest(pt); + int button_count = DlgMain.toolbar_wm.toolbar->GetButtonCount(); + DWORD button_style = DlgMain.toolbar_wm.toolbar->GetButtonStyle(button_index); + + if (button_index > -1 && + button_index < button_count && + button_index != DlgMain.toolbar_wm.button_index) { + if (button_style & BTNS_DROPDOWN || button_style & BTNS_WHOLEDROPDOWN) { + DlgMain.toolbar_wm.toolbar->SendMessage(TB_SETHOTITEM, button_index, 0); + return 0L; + } + } + + break; + } + } + } + + return CallNextHookEx(DlgMain.toolbar_wm.hook, code, wParam, lParam); +} + +LRESULT MainDialog::OnToolbarNotify(LPARAM lParam) { + switch (reinterpret_cast(lParam)->code) { + // Dropdown button click + case TBN_DROPDOWN: { + LPNMTOOLBAR nmt = reinterpret_cast(lParam); + int toolbar_id = nmt->hdr.idFrom; + switch (toolbar_id) { + case IDC_TOOLBAR_MENU: + toolbar_wm.toolbar = &toolbar_menu; + break; + case IDC_TOOLBAR_MAIN: + toolbar_wm.toolbar = &toolbar_main; + break; + case IDC_TOOLBAR_SEARCH: + toolbar_wm.toolbar = &toolbar_search; + break; + } + toolbar_wm.ShowMenu(); + break; + } + + // Show tooltips + case TBN_GETINFOTIP: { + NMTBGETINFOTIP* git = reinterpret_cast(lParam); + git->cchTextMax = INFOTIPSIZE; + // Main toolbar + if (git->hdr.hwndFrom == toolbar_main.GetWindowHandle()) { + git->pszText = (LPWSTR)(toolbar_main.GetButtonTooltip(git->lParam)); + // Search toolbar + } else if (git->hdr.hwndFrom == toolbar_search.GetWindowHandle()) { + git->pszText = (LPWSTR)(toolbar_search.GetButtonTooltip(git->lParam)); + } + break; + } + + // Hot-tracking + case TBN_HOTITEMCHANGE: { + LPNMTBHOTITEM lpnmhi = reinterpret_cast(lParam); + if (toolbar_wm.hook && lpnmhi->idNew > 0) { + toolbar_wm.toolbar->PressButton(lpnmhi->idOld, FALSE); + toolbar_wm.toolbar->SendMessage(lpnmhi->idNew, TRUE); + SendMessage(WM_CANCELMODE); + PostMessage(WM_TAIGA_SHOWMENU); + } + break; + } + } + + return 0L; +} + +void MainDialog::ToolbarWithMenu::ShowMenu() { + button_index = toolbar->SendMessage(TB_GETHOTITEM); + + TBBUTTON tbb; + toolbar->GetButton(button_index, tbb); + toolbar->PressButton(tbb.idCommand, TRUE); + + // Calculate point + RECT rect; + toolbar->SendMessage(TB_GETITEMRECT, button_index, reinterpret_cast(&rect)); + POINT pt = {rect.left, rect.bottom}; + ClientToScreen(toolbar->GetWindowHandle(), &pt); + + // Hook + if (hook) UnhookWindowsHookEx(hook); + hook = SetWindowsHookEx(WH_MSGFILTER, &HookProc, NULL, GetCurrentThreadId()); + + // Display menu + std::wstring action; + HWND hwnd = DlgMain.GetWindowHandle(); + #define SHOWUIMENU(id, name) \ + case id: action = ui::Menus.Show(hwnd, pt.x, pt.y, name); break; + switch (tbb.idCommand) { + SHOWUIMENU(100, L"File"); + SHOWUIMENU(101, L"Services"); + SHOWUIMENU(102, L"Tools"); + SHOWUIMENU(103, L"View"); + SHOWUIMENU(104, L"Help"); + SHOWUIMENU(kToolbarButtonFolders, L"Folders"); + SHOWUIMENU(kToolbarButtonTools, L"ExternalLinks"); + } + #undef SHOWUIMENU + + // Unhook + if (hook) { + UnhookWindowsHookEx(hook); + hook = nullptr; + } + + toolbar->PressButton(tbb.idCommand, FALSE); + + if (!action.empty()) { + ExecuteAction(action); + ui::Menus.UpdateAll(DlgAnimeList.GetCurrentItem()); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +/* Statusbar */ + +LRESULT MainDialog::StatusBar::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_SETCURSOR: { + POINT pt; + GetCursorPos(&pt); + ScreenToClient(GetWindowHandle(), &pt); + win::Rect rect; + GetRect(1, &rect); + if (rect.PtIn(pt)) { + SetSharedCursor(IDC_HAND); + return TRUE; + } + break; + } + } + + return WindowProcDefault(hwnd, uMsg, wParam, lParam); +} + +LRESULT MainDialog::OnStatusbarNotify(LPARAM lParam) { + auto pnmh = reinterpret_cast(lParam); + + switch (pnmh->code) { + case NM_CLICK: { + auto pnm = reinterpret_cast(lParam); + // Handle click on timer + if (pnm->dwItemSpec == 1) { + if (ui::OnRecognitionCancelConfirm()) { + CurrentEpisode.processed = true; + } + return TRUE; + } + break; + } + } + + return 0L; +} + +} // namespace ui \ No newline at end of file diff --git a/dlg/dlg_search.cpp b/src/ui/dlg/dlg_search.cpp similarity index 50% rename from dlg/dlg_search.cpp rename to src/ui/dlg/dlg_search.cpp index 881ffdda8..6f46b0b23 100644 --- a/dlg/dlg_search.cpp +++ b/src/ui/dlg/dlg_search.cpp @@ -1,224 +1,196 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_search.h" -#include "dlg_main.h" - -#include "../anime_db.h" -#include "../common.h" -#include "../gfx.h" -#include "../http.h" -#include "../myanimelist.h" -#include "../resource.h" -#include "../settings.h" -#include "../string.h" -#include "../taiga.h" -#include "../theme.h" -#include "../xml.h" - -#include "../win32/win_taskdialog.h" - -class SearchDialog SearchDialog; - -// ============================================================================= - -BOOL SearchDialog::OnInitDialog() { - // Create list control - list_.Attach(GetDlgItem(IDC_LIST_SEARCH)); - list_.SetExtendedStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP); - list_.SetImageList(UI.ImgList16.GetHandle()); - list_.SetTheme(); - list_.InsertColumn(0, 400, 400, LVCFMT_LEFT, L"Anime title"); - list_.InsertColumn(1, 60, 60, LVCFMT_CENTER, L"Type"); - list_.InsertColumn(2, 60, 60, LVCFMT_RIGHT, L"Episodes"); - list_.InsertColumn(3, 60, 60, LVCFMT_RIGHT, L"Score"); - list_.InsertColumn(4, 100, 100, LVCFMT_RIGHT, L"Season"); - list_.EnableGroupView(true); - list_.InsertGroup(0, L"Not in list"); - list_.InsertGroup(1, L"In list"); - - // Success - return TRUE; -} - -INT_PTR SearchDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - // Forward mouse wheel messages to the list - case WM_MOUSEWHEEL: { - return list_.SendMessage(uMsg, wParam, lParam); - } - } - - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - -LRESULT SearchDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { - // ListView control - if (idCtrl == IDC_LIST_SEARCH) { - switch (pnmh->code) { - // Column click - case LVN_COLUMNCLICK: { - LPNMLISTVIEW lplv = (LPNMLISTVIEW)pnmh; - int order = 1; - if (lplv->iSubItem == list_.GetSortColumn()) order = list_.GetSortOrder() * -1; - switch (lplv->iSubItem) { - // Episode - case 2: - list_.Sort(lplv->iSubItem, order, LIST_SORTTYPE_NUMBER, ListViewCompareProc); - break; - // Season - case 4: - list_.Sort(lplv->iSubItem, order, LIST_SORTTYPE_STARTDATE, ListViewCompareProc); - break; - // Other columns - default: - list_.Sort(lplv->iSubItem, order, LIST_SORTTYPE_DEFAULT, ListViewCompareProc); - break; - } - break; - } - - // Double click - case NM_DBLCLK: { - if (list_.GetSelectedCount() > 0) { - LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); - if (lpnmitem->iItem == -1) break; - LPARAM lParam = list_.GetItemParam(lpnmitem->iItem); - if (lParam) ExecuteAction(L"Info", 0, lParam); - } - break; - } - - // Right click - case NM_RCLICK: { - LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); - if (lpnmitem->iItem == -1) break; - LPARAM lParam = list_.GetItemParam(lpnmitem->iItem); - auto anime_item = AnimeDatabase.FindItem(static_cast(lParam)); - UpdateSearchListMenu(!anime_item->IsInList()); - ExecuteAction(UI.Menus.Show(pnmh->hwndFrom, 0, 0, L"SearchList"), 0, lParam); - break; - } - } - } - - return 0; -} - -void SearchDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { - switch (uMsg) { - case WM_SIZE: { - win32::Rect rcWindow; - rcWindow.Set(0, 0, size.cx, size.cy); - // Resize list - list_.SetPosition(nullptr, rcWindow); - } - } -} - -// ============================================================================= - -void SearchDialog::ParseResults(const wstring& data) { - if (data.empty()) { - wstring msg = L"No results found for \"" + search_text + L"\"."; - win32::TaskDialog dlg(L"Search Anime", TD_INFORMATION_ICON); - dlg.SetMainInstruction(msg.c_str()); - dlg.AddButton(L"OK", IDOK); - dlg.Show(GetWindowHandle()); - return; - } - - if (data == L"Invalid credentials") { - win32::TaskDialog dlg(L"Search Anime", TD_ERROR_ICON); - dlg.SetMainInstruction(L"Invalid username or password."); - dlg.SetContent(L"Anime search requires authentication, which means, you need to " - L"enter a valid username and password to search MyAnimeList."); - dlg.AddButton(L"OK", IDOK); - dlg.Show(GetWindowHandle()); - return; - } - - anime_ids_.clear(); - xml_document doc; - xml_parse_result result = doc.load(data.c_str()); - if (result.status == status_ok) { - xml_node anime = doc.child(L"anime"); - for (xml_node entry = anime.child(L"entry"); entry; entry = entry.next_sibling(L"entry")) { - anime_ids_.push_back(XML_ReadIntValue(entry, L"id")); - } - } - - mal::ParseSearchResult(data); - - RefreshList(); -} - -void SearchDialog::AddAnimeToList(int anime_id) { - auto anime_item = AnimeDatabase.FindItem(anime_id); - - if (anime_item) { - int i = list_.GetItemCount(); - list_.InsertItem(i, anime_item->IsInList() ? 1 : 0, - StatusToIcon(anime_item->GetAiringStatus()), 0, nullptr, - anime_item->GetTitle().c_str(), - static_cast(anime_item->GetId())); - list_.SetItem(i, 1, mal::TranslateType(anime_item->GetType()).c_str()); - list_.SetItem(i, 2, mal::TranslateNumber(anime_item->GetEpisodeCount()).c_str()); - list_.SetItem(i, 3, anime_item->GetScore().c_str()); - list_.SetItem(i, 4, mal::TranslateDateToSeason(anime_item->GetDate(anime::DATE_START)).c_str()); - } -} - -void SearchDialog::RefreshList() { - if (!IsWindow()) return; - - // Hide and clear the list - list_.Hide(); - list_.DeleteAllItems(); - - // Add anime items to list - for (size_t i = 0; i < anime_ids_.size(); i++) { - AddAnimeToList(anime_ids_.at(i)); - } - /*for (auto it = AnimeDatabase.items.begin(); it != AnimeDatabase.items.end(); ++it) { - if (std::find(anime_ids_.begin(), anime_ids_.end(), it->second.GetId()) == anime_ids_.end()) - if (filters_.CheckItem(it->second)) - AddAnimeToList(it->second.GetId()); - }*/ - - // Sort and show the list again - //list_.Sort(0, 1, LIST_SORTTYPE_DEFAULT, ListViewCompareProc); - list_.Show(SW_SHOW); -} - -bool SearchDialog::Search(const wstring& title) { - anime_ids_.clear(); - search_text = title; - filters_.text = title; - //RefreshList(); - - if (mal::SearchAnime(anime::ID_UNKNOWN, title)) { - MainDialog.ChangeStatus(L"Searching MyAnimeList for \"" + title + L"\"..."); - return true; - } else { - MainDialog.ChangeStatus(L"An error occured while searching MyAnimeList for \"" + title + L"\"..."); - return false; - } -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "sync/sync.h" +#include "taiga/resource.h" +#include "taiga/script.h" +#include "taiga/settings.h" +#include "ui/dlg/dlg_search.h" +#include "ui/dialog.h" +#include "ui/list.h" +#include "ui/menu.h" +#include "ui/theme.h" +#include "ui/ui.h" +#include "win/win_taskdialog.h" + +namespace ui { + +SearchDialog DlgSearch; + +BOOL SearchDialog::OnInitDialog() { + // Create list control + list_.Attach(GetDlgItem(IDC_LIST_SEARCH)); + list_.SetExtendedStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP); + list_.SetImageList(ui::Theme.GetImageList16().GetHandle()); + list_.SetTheme(); + list_.InsertColumn(0, 400, 400, LVCFMT_LEFT, L"Anime title"); + list_.InsertColumn(1, 60, 60, LVCFMT_CENTER, L"Type"); + list_.InsertColumn(2, 60, 60, LVCFMT_RIGHT, L"Episodes"); + list_.InsertColumn(3, 60, 60, LVCFMT_RIGHT, L"Score"); + list_.InsertColumn(4, 100, 100, LVCFMT_RIGHT, L"Season"); + list_.EnableGroupView(true); + list_.InsertGroup(0, L"Not in list"); + list_.InsertGroup(1, L"In list"); + + // Success + return TRUE; +} + +INT_PTR SearchDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + // Forward mouse wheel messages to the list + case WM_MOUSEWHEEL: { + return list_.SendMessage(uMsg, wParam, lParam); + } + } + + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + +LRESULT SearchDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { + // ListView control + if (idCtrl == IDC_LIST_SEARCH) { + switch (pnmh->code) { + // Column click + case LVN_COLUMNCLICK: { + LPNMLISTVIEW lplv = (LPNMLISTVIEW)pnmh; + int order = 1; + if (lplv->iSubItem == list_.GetSortColumn()) + order = list_.GetSortOrder() * -1; + switch (lplv->iSubItem) { + // Episode + case 2: + list_.Sort(lplv->iSubItem, order, ui::kListSortNumber, ui::ListViewCompareProc); + break; + // Season + case 4: + list_.Sort(lplv->iSubItem, order, ui::kListSortDateStart, ui::ListViewCompareProc); + break; + // Other columns + default: + list_.Sort(lplv->iSubItem, order, ui::kListSortDefault, ui::ListViewCompareProc); + break; + } + break; + } + + // Double click + case NM_DBLCLK: { + if (list_.GetSelectedCount() > 0) { + LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); + if (lpnmitem->iItem == -1) + break; + LPARAM lParam = list_.GetItemParam(lpnmitem->iItem); + if (lParam) + ShowDlgAnimeInfo(lParam); + } + break; + } + + // Right click + case NM_RCLICK: { + LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); + if (lpnmitem->iItem == -1) + break; + LPARAM lParam = list_.GetItemParam(lpnmitem->iItem); + auto anime_item = AnimeDatabase.FindItem(static_cast(lParam)); + ui::Menus.UpdateSearchList(!anime_item->IsInList()); + ExecuteAction(ui::Menus.Show(pnmh->hwndFrom, 0, 0, L"SearchList"), 0, lParam); + break; + } + } + } + + return 0; +} + +void SearchDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { + switch (uMsg) { + case WM_SIZE: { + win::Rect rcWindow; + rcWindow.Set(0, 0, size.cx, size.cy); + // Resize list + list_.SetPosition(nullptr, rcWindow); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void SearchDialog::ParseResults(const std::vector& ids) { + if (!IsWindow()) + return; + + if (ids.empty()) { + std::wstring msg = L"No results found for \"" + search_text + L"\"."; + win::TaskDialog dlg(L"Search Anime", TD_INFORMATION_ICON); + dlg.SetMainInstruction(msg.c_str()); + dlg.AddButton(L"OK", IDOK); + dlg.Show(GetWindowHandle()); + return; + } + + anime_ids_ = ids; + RefreshList(); +} + +void SearchDialog::AddAnimeToList(int anime_id) { + auto anime_item = AnimeDatabase.FindItem(anime_id); + + if (anime_item) { + int i = list_.GetItemCount(); + list_.InsertItem(i, anime_item->IsInList() ? 1 : 0, + StatusToIcon(anime_item->GetAiringStatus()), 0, nullptr, + anime_item->GetTitle().c_str(), + static_cast(anime_item->GetId())); + list_.SetItem(i, 1, anime::TranslateType(anime_item->GetType()).c_str()); + list_.SetItem(i, 2, anime::TranslateNumber(anime_item->GetEpisodeCount()).c_str()); + list_.SetItem(i, 3, anime_item->GetScore().c_str()); + list_.SetItem(i, 4, anime::TranslateDateToSeasonString(anime_item->GetDateStart()).c_str()); + } +} + +void SearchDialog::RefreshList() { + if (!IsWindow()) + return; + + // Hide and clear the list + list_.Hide(); + list_.DeleteAllItems(); + + // Add anime items to list + foreach_(it, anime_ids_) + AddAnimeToList(*it); + + // Show the list again + list_.Show(SW_SHOW); +} + +void SearchDialog::Search(const std::wstring& title) { + anime_ids_.clear(); + search_text = title; + + ui::ChangeStatusText(L"Searching " + taiga::GetCurrentService()->name() + + L" for \"" + title + L"\"..."); + sync::SearchTitle(title, anime::ID_UNKNOWN); +} + +} // namespace ui \ No newline at end of file diff --git a/dlg/dlg_search.h b/src/ui/dlg/dlg_search.h similarity index 57% rename from dlg/dlg_search.h rename to src/ui/dlg/dlg_search.h index 9536506a9..154123300 100644 --- a/dlg/dlg_search.h +++ b/src/ui/dlg/dlg_search.h @@ -1,58 +1,56 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_SEARCH_H -#define DLG_SEARCH_H - -#include "../std.h" - -#include "../anime_filter.h" - -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" - -// ============================================================================= - -class SearchDialog : public win32::Dialog { -public: - SearchDialog() {}; - virtual ~SearchDialog() {}; - - INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - BOOL OnInitDialog(); - LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); - void OnSize(UINT uMsg, UINT nType, SIZE size); - -public: - void AddAnimeToList(int anime_id); - void ParseResults(const wstring& data); - void RefreshList(); - bool Search(const wstring& title); - -public: - wstring search_text; - -private: - vector anime_ids_; - anime::Filters filters_; - win32::ListView list_; -}; - -extern class SearchDialog SearchDialog; - -#endif // DLG_SEARCH_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_SEARCH_H +#define TAIGA_UI_DLG_SEARCH_H + +#include +#include + +#include "win/ctrl/win_ctrl.h" +#include "win/win_dialog.h" + +namespace ui { + +class SearchDialog : public win::Dialog { +public: + SearchDialog() {}; + ~SearchDialog() {}; + + INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + BOOL OnInitDialog(); + LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); + void OnSize(UINT uMsg, UINT nType, SIZE size); + + void AddAnimeToList(int anime_id); + void ParseResults(const std::vector& ids); + void RefreshList(); + void Search(const std::wstring& title); + + std::wstring search_text; + +private: + std::vector anime_ids_; + win::ListView list_; +}; + +extern SearchDialog DlgSearch; + +} // namespace ui + +#endif // TAIGA_UI_DLG_SEARCH_H \ No newline at end of file diff --git a/dlg/dlg_season.cpp b/src/ui/dlg/dlg_season.cpp similarity index 52% rename from dlg/dlg_season.cpp rename to src/ui/dlg/dlg_season.cpp index 2b4a34646..d2fbd5a23 100644 --- a/dlg/dlg_season.cpp +++ b/src/ui/dlg/dlg_season.cpp @@ -1,667 +1,688 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" -#include - -#include "dlg_main.h" -#include "dlg_season.h" - -#include "../anime_db.h" -#include "../common.h" -#include "../gfx.h" -#include "../myanimelist.h" -#include "../resource.h" -#include "../settings.h" -#include "../string.h" -#include "../taiga.h" -#include "../theme.h" - -#include "../win32/win_gdi.h" - -class SeasonDialog SeasonDialog; - -// ============================================================================= - -SeasonDialog::SeasonDialog() - : group_by(SEASON_GROUPBY_TYPE), - sort_by(SEASON_SORTBY_TITLE) { -} - -BOOL SeasonDialog::OnInitDialog() { - // Set properties - SetSizeMin(575, 310); - SetIconLarge(IDI_MAIN); - SetIconSmall(IDI_MAIN); - - // Create list - list_.Attach(GetDlgItem(IDC_LIST_SEASON)); - list_.EnableGroupView(true); - list_.SetExtendedStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT); - list_.SetTheme(); - list_.SetView(LV_VIEW_TILE); - SetViewMode(SEASON_VIEWAS_TILES); - - // Create main toolbar - toolbar_.Attach(GetDlgItem(IDC_TOOLBAR_SEASON)); - toolbar_.SetImageList(UI.ImgList16.GetHandle(), 16, 16); - toolbar_.SendMessage(TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS | TBSTYLE_EX_MIXEDBUTTONS); - - // Insert toolbar buttons - BYTE fsState = TBSTATE_ENABLED; - BYTE fsStyle1 = BTNS_AUTOSIZE | BTNS_SHOWTEXT; - BYTE fsStyle2 = BTNS_AUTOSIZE | BTNS_SHOWTEXT | BTNS_WHOLEDROPDOWN; - toolbar_.InsertButton(0, ICON16_CALENDAR, 100, fsState, fsStyle2, 0, L"Select season", nullptr); - toolbar_.InsertButton(1, ICON16_REFRESH, 101, fsState, fsStyle1, 1, L"Refresh data", L"Download anime details and missing images"); - toolbar_.InsertButton(2, 0, 0, 0, BTNS_SEP, 0, nullptr, nullptr); - toolbar_.InsertButton(3, ICON16_CATEGORY, 103, fsState, fsStyle2, 3, L"Group by", nullptr); - toolbar_.InsertButton(4, ICON16_SORT, 104, fsState, fsStyle2, 4, L"Sort by", nullptr); - toolbar_.InsertButton(5, ICON16_DETAILS, 105, fsState, fsStyle2, 5, L"View", nullptr); - toolbar_.InsertButton(6, 0, 0, 0, BTNS_SEP, 0, nullptr, nullptr); - toolbar_.InsertButton(7, ICON16_BALLOON, 107, fsState, fsStyle1, 7, L"Discuss", L""); - - // Create rebar - rebar_.Attach(GetDlgItem(IDC_REBAR_SEASON)); - // Insert rebar bands - UINT fMask = RBBIM_CHILD | RBBIM_CHILDSIZE | RBBIM_SIZE | RBBIM_STYLE; - UINT fStyle = RBBS_NOGRIPPER; - rebar_.InsertBand(nullptr, 0, 0, 0, 0, 0, 0, 0, 0, fMask, fStyle); - rebar_.InsertBand(toolbar_.GetWindowHandle(), GetSystemMetrics(SM_CXSCREEN), 0, 0, 0, 0, 0, 0, - HIWORD(toolbar_.GetButtonSize()) + (HIWORD(toolbar_.GetPadding()) / 2), fMask, fStyle); - - // Refresh - RefreshList(); - RefreshStatus(); - RefreshToolbar(); - - return TRUE; -} - -// ============================================================================= - -INT_PTR SeasonDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - // Forward mouse wheel messages to the list - case WM_MOUSEWHEEL: { - return list_.SendMessage(uMsg, wParam, lParam); - } - } - - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - -BOOL SeasonDialog::OnCommand(WPARAM wParam, LPARAM lParam) { - // Toolbar - switch (LOWORD(wParam)) { - // Refresh data - case 101: - RefreshData(); - return TRUE; - // Discuss - case 107: - mal::ViewSeasonGroup(); - return TRUE; - } - - return FALSE; -} - -BOOL SeasonDialog::OnDestroy() { - return TRUE; -} - -LRESULT SeasonDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { - // List - if (idCtrl == IDC_LIST_SEASON) { - return OnListNotify(reinterpret_cast(pnmh)); - // Toolbar - } else if (idCtrl == IDC_TOOLBAR_SEASON) { - return OnToolbarNotify(reinterpret_cast(pnmh)); - } - - return 0; -} - -void SeasonDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { - switch (uMsg) { - case WM_SIZE: { - win32::Rect rcWindow; - rcWindow.Set(0, 0, size.cx, size.cy); - // Resize rebar - rebar_.SendMessage(WM_SIZE, 0, 0); - rcWindow.top += rebar_.GetBarHeight() + ScaleY(WIN_CONTROL_MARGIN / 2); - // Resize list - list_.SetPosition(nullptr, rcWindow); - } - } -} - -// ============================================================================= - -LRESULT SeasonDialog::ListView::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_MOUSEWHEEL: { - /*if (this->GetItemCount() > 0) { - short delta = GET_WHEEL_DELTA_WPARAM(wParam); - short value = 200; - if (delta > 0) value = -value; - SendMessage(LVM_SCROLL, 0, value); - return 0; - }*/ - break; - } - } - - return WindowProcDefault(hwnd, uMsg, wParam, lParam); -} - -LRESULT SeasonDialog::OnListNotify(LPARAM lParam) { - LPNMHDR pnmh = reinterpret_cast(lParam); - switch (pnmh->code) { - // Custom draw - case NM_CUSTOMDRAW: { - return OnListCustomDraw(lParam); - } - - // Double click - case NM_DBLCLK: { - LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); - if (lpnmitem->iItem == -1) break; - LPARAM param = list_.GetItemParam(lpnmitem->iItem); - if (param) ExecuteAction(L"Info", 0, param); - break; - } - - // Right click - case NM_RCLICK: { - LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); - if (lpnmitem->iItem == -1) break; - auto anime_item = AnimeDatabase.FindItem( - static_cast(list_.GetItemParam(lpnmitem->iItem))); - if (anime_item) { - UpdateSeasonListMenu(!anime_item->IsInList()); - ExecuteAction(UI.Menus.Show(pnmh->hwndFrom, 0, 0, L"SeasonList"), 0, - static_cast(anime_item->GetId())); - list_.RedrawWindow(); - } - break; - } - } - - return 0; -} - -LRESULT SeasonDialog::OnListCustomDraw(LPARAM lParam) { - LRESULT result = CDRF_DODEFAULT; - LPNMLVCUSTOMDRAW pCD = reinterpret_cast(lParam); - - win32::Dc hdc = pCD->nmcd.hdc; - win32::Rect rect = pCD->nmcd.rc; - - if (win32::GetWinVersion() < win32::VERSION_VISTA) { - list_.GetSubItemRect(pCD->nmcd.dwItemSpec, pCD->iSubItem, &rect); - } - - switch (pCD->nmcd.dwDrawStage) { - case CDDS_PREPAINT: { - // LVN_GETEMPTYMARKUP notification is sent only once, so we paint our own - // markup text when the control has no items. - if (list_.GetItemCount() == 0) { - wstring text; - if (SeasonDatabase.items.empty()) { - text = L"No season selected. Please choose one from above."; - } else { - text = L"No matching items for \"" + MainDialog.search_bar.filters.text + L"\"."; - } - hdc.EditFont(L"Segoe UI", 9, -1, TRUE); - hdc.SetBkMode(TRANSPARENT); - hdc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); - hdc.DrawText(text.c_str(), text.length(), rect, - DT_CENTER | DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER); - } - result = CDRF_NOTIFYITEMDRAW; - break; - } - - case CDDS_ITEMPREPAINT: { - result = CDRF_NOTIFYPOSTPAINT; - break; - } - - case CDDS_ITEMPOSTPAINT: { - auto anime_item = AnimeDatabase.FindItem(static_cast(pCD->nmcd.lItemlParam)); - if (!anime_item) break; - - // Draw border - if (win32::GetWinVersion() > win32::VERSION_XP) { - rect.Inflate(-4, -4); - } - if (win32::GetWinVersion() < win32::VERSION_VISTA && - pCD->nmcd.uItemState & CDIS_SELECTED) { - hdc.FillRect(rect, GetSysColor(COLOR_HIGHLIGHT)); - } else { - hdc.FillRect(rect, theme::COLOR_GRAY); - } - - // Draw background - rect.Inflate(-1, -1); - hdc.FillRect(rect, theme::COLOR_LIGHTGRAY); - - // Calculate text height - int text_height = GetTextHeight(hdc.Get()); - - // Calculate areas - win32::Rect rect_image( - rect.left + 4, rect.top + 4, - rect.left + 124, rect.bottom - 4); - win32::Rect rect_title( - rect_image.right + 4, rect_image.top, - rect.right - 4, rect_image.top + text_height + 8); - win32::Rect rect_details( - rect_title.left + 4, rect_title.bottom + 4, - rect_title.right, rect_title.bottom + 4 + (6 * (text_height + 2))); - win32::Rect rect_synopsis( - rect_details.left, rect_details.bottom + 4, - rect_details.right, rect_image.bottom); - - // Draw image - if (ImageDatabase.Load(anime_item->GetId(), false, false)) { - auto image = ImageDatabase.GetImage(anime_item->GetId()); - rect_image = ResizeRect(rect_image, - image->rect.Width(), - image->rect.Height(), - true, true, false); - hdc.SetStretchBltMode(HALFTONE); - hdc.StretchBlt(rect_image.left, rect_image.top, - rect_image.Width(), rect_image.Height(), - image->dc.Get(), 0, 0, - image->rect.Width(), - image->rect.Height(), - SRCCOPY); - } - - // Draw title background - COLORREF color; - switch (anime_item->GetAiringStatus()) { - case mal::STATUS_AIRING: - color = theme::COLOR_LIGHTGREEN; break; - case mal::STATUS_FINISHED: default: - color = theme::COLOR_LIGHTBLUE; break; - case mal::STATUS_NOTYETAIRED: - color = theme::COLOR_LIGHTRED; break; - } - if (view_as == SEASON_VIEWAS_IMAGES) { - rect_title.Copy(rect); - rect_title.top = rect_title.bottom - (text_height + 8); - } - hdc.FillRect(rect_title, color); - - // Draw anime list indicator - if (anime_item->IsInList()) { - UI.ImgList16.Draw(ICON16_DOCUMENT_A, hdc.Get(), - rect_title.right - 20, rect_title.top + 4); - rect_title.right -= 20; - } - - // Set title - wstring text = anime_item->GetTitle(); - if (view_as == SEASON_VIEWAS_IMAGES) { - switch (sort_by) { - case SEASON_SORTBY_AIRINGDATE: - text = mal::TranslateDate(anime_item->GetDate(anime::DATE_START)); - break; - case SEASON_SORTBY_EPISODES: - text = mal::TranslateNumber(anime_item->GetEpisodeCount(), L""); - if (text.empty()) { - text = L"Unknown"; - } else { - text += text == L"1" ? L" episode" : L" episodes"; - } - break; - case SEASON_SORTBY_POPULARITY: - text = anime_item->GetPopularity(); - if (text.empty()) text = L"#0"; - break; - case SEASON_SORTBY_SCORE: - text = anime_item->GetScore(); - if (InStr(text, L"scored by") > -1) - text = text.substr(0, 4); - if (text.empty()) text = L"0.00"; - break; - } - } - - // Draw title - rect_title.Inflate(-4, 0); - hdc.EditFont(nullptr, -1, TRUE); - hdc.SetBkMode(TRANSPARENT); - UINT nFormat = DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER; - if (view_as == SEASON_VIEWAS_IMAGES) - nFormat |= DT_CENTER; - hdc.DrawText(text.c_str(), text.length(), rect_title, nFormat); - - // Draw details - if (view_as == SEASON_VIEWAS_IMAGES) break; - int text_top = rect_details.top; - #define DRAWLINE(t) \ - text = t; \ - hdc.DrawText(text.c_str(), text.length(), rect_details, \ - DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE); \ - rect_details.Offset(0, text_height + 2); - - DRAWLINE(L"Aired:"); - DRAWLINE(L"Episodes:"); - DRAWLINE(L"Genres:"); - DRAWLINE(L"Producers:"); - DRAWLINE(L"Score:"); - DRAWLINE(L"Popularity:"); - - rect_details.Set(rect_details.left + 75, text_top, - rect_details.right, rect_details.top + text_height); - DeleteObject(hdc.DetachFont()); - - text = mal::TranslateDate(anime_item->GetDate(anime::DATE_START)); - text += anime_item->GetDate(anime::DATE_END) != anime_item->GetDate(anime::DATE_START) ? - L" to " + mal::TranslateDate(anime_item->GetDate(anime::DATE_END)) : L""; - text += L" (" + mal::TranslateStatus(anime_item->GetAiringStatus()) + L")"; - DRAWLINE(text); - DRAWLINE(mal::TranslateNumber(anime_item->GetEpisodeCount(), L"Unknown")); - DRAWLINE(anime_item->GetGenres().empty() ? L"?" : anime_item->GetGenres()); - DRAWLINE(anime_item->GetProducers().empty() ? L"?" : anime_item->GetProducers()); - DRAWLINE(anime_item->GetScore().empty() ? L"0.00" : anime_item->GetScore()); - DRAWLINE(anime_item->GetPopularity().empty() ? L"#0" : anime_item->GetPopularity()); - - #undef DRAWLINE - - // Draw synopsis - if (!anime_item->GetSynopsis().empty()) { - text = anime_item->GetSynopsis(); - // DT_WORDBREAK doesn't go well with DT_*_ELLIPSIS, so we need to make - // sure our text ends with ellipses by clipping that extra pixel. - rect_synopsis.bottom -= (rect_synopsis.Height() % text_height) + 1; - hdc.DrawText(text.c_str(), text.length(), rect_synopsis, - DT_END_ELLIPSIS | DT_NOPREFIX | DT_WORDBREAK); - } - - break; - } - } - - hdc.DetachDC(); - return result; -} - -LRESULT SeasonDialog::OnToolbarNotify(LPARAM lParam) { - switch (reinterpret_cast(lParam)->code) { - // Dropdown button click - case TBN_DROPDOWN: { - RECT rect; LPNMTOOLBAR nmt = reinterpret_cast(lParam); - ::SendMessage(nmt->hdr.hwndFrom, TB_GETRECT, static_cast(nmt->iItem), reinterpret_cast(&rect)); - MapWindowPoints(nmt->hdr.hwndFrom, HWND_DESKTOP, reinterpret_cast(&rect), 2); - UpdateSeasonMenu(); - wstring action; - switch (LOWORD(nmt->iItem)) { - // Select season - case 100: - action = UI.Menus.Show(m_hWindow, rect.left, rect.bottom, L"SeasonSelect"); - break; - // Group by - case 103: - action = UI.Menus.Show(m_hWindow, rect.left, rect.bottom, L"SeasonGroup"); - break; - // Sort by - case 104: - action = UI.Menus.Show(m_hWindow, rect.left, rect.bottom, L"SeasonSort"); - break; - // View as - case 105: - action = UI.Menus.Show(m_hWindow, rect.left, rect.bottom, L"SeasonView"); - break; - } - if (!action.empty()) { - ExecuteAction(action); - } - break; - } - - // Show tooltips - case TBN_GETINFOTIP: { - NMTBGETINFOTIP* git = reinterpret_cast(lParam); - git->cchTextMax = INFOTIPSIZE; - if (git->hdr.hwndFrom == toolbar_.GetWindowHandle()) { - git->pszText = const_cast(toolbar_.GetButtonTooltip(git->lParam)); - } - break; - } - } - - return 0L; -} - -// ============================================================================= - -void SeasonDialog::RefreshData(int anime_id) { - for (auto id = SeasonDatabase.items.begin(); id != SeasonDatabase.items.end(); ++id) { - if (anime_id > 0 && anime_id != *id) - continue; - - auto anime_item = AnimeDatabase.FindItem(*id); - if (!anime_item) - continue; - - // Download missing image - ImageDatabase.Load(*id, true, true); - - // Get details - if (anime_item->IsOldEnough() || anime_item->GetSynopsis().empty()) - mal::SearchAnime(*id, anime_item->GetTitle()); - } -} - -void SeasonDialog::RefreshList(bool redraw_only) { - if (!IsWindow()) return; - - if (redraw_only) { - list_.RedrawWindow(); - return; - } - - // Set title - if (SeasonDatabase.name.empty()) { - SetText(L"Season Browser"); - } else { - SetText(L"Season Browser - " + SeasonDatabase.name); - } - - // Disable drawing - list_.SetRedraw(FALSE); - - // Insert list groups - list_.RemoveAllGroups(); - list_.EnableGroupView(true); // Required for XP - switch (group_by) { - case SEASON_GROUPBY_AIRINGSTATUS: - for (int i = mal::STATUS_AIRING; i <= mal::STATUS_NOTYETAIRED; i++) { - list_.InsertGroup(i, mal::TranslateStatus(i).c_str(), true, false); - } - break; - case SEASON_GROUPBY_LISTSTATUS: - for (int i = mal::MYSTATUS_NOTINLIST; i <= mal::MYSTATUS_PLANTOWATCH; i++) { - list_.InsertGroup(i, mal::TranslateMyStatus(i, false).c_str(), true, false); - } - break; - case SEASON_GROUPBY_TYPE: - for (int i = mal::TYPE_TV; i <= mal::TYPE_MUSIC; i++) { - list_.InsertGroup(i, mal::TranslateType(i).c_str(), true, false); - } - break; - } - - // Filter - vector filters; - Split(MainDialog.search_bar.filters.text, L" ", filters); - RemoveEmptyStrings(filters); - - // Add items - list_.DeleteAllItems(); - for (auto i = SeasonDatabase.items.begin(); i != SeasonDatabase.items.end(); ++i) { - auto anime_item = AnimeDatabase.FindItem(*i); - bool passed_filters = true; - for (auto j = filters.begin(); j != filters.end(); ++j) { - if (InStr(anime_item->GetGenres(), *j, 0, true) == -1 && - InStr(anime_item->GetProducers(), *j, 0, true) == -1 && - InStr(anime_item->GetTitle(), *j, 0, true) == -1) { - passed_filters = false; - break; - } - } - if (!passed_filters) continue; - int group = -1; - switch (group_by) { - case SEASON_GROUPBY_AIRINGSTATUS: - group = anime_item->GetAiringStatus(); - break; - case SEASON_GROUPBY_LISTSTATUS: { - group = anime_item->GetMyStatus(); - break; - } - case SEASON_GROUPBY_TYPE: - group = anime_item->GetType(); - break; - } - list_.InsertItem(i - SeasonDatabase.items.begin(), - group, -1, 0, nullptr, LPSTR_TEXTCALLBACK, - static_cast(anime_item->GetId())); - } - - // Sort items - switch (sort_by) { - case SEASON_SORTBY_AIRINGDATE: - list_.Sort(0, -1, LIST_SORTTYPE_STARTDATE, ListViewCompareProc); - break; - case SEASON_SORTBY_EPISODES: - list_.Sort(0, -1, LIST_SORTTYPE_EPISODES, ListViewCompareProc); - break; - case SEASON_SORTBY_POPULARITY: - list_.Sort(0, 1, LIST_SORTTYPE_POPULARITY, ListViewCompareProc); - break; - case SEASON_SORTBY_SCORE: - list_.Sort(0, -1, LIST_SORTTYPE_SCORE, ListViewCompareProc); - break; - case SEASON_SORTBY_TITLE: - list_.Sort(0, 1, LIST_SORTTYPE_TITLE, ListViewCompareProc); - break; - } - - // Redraw - list_.SetRedraw(TRUE); - list_.RedrawWindow(nullptr, nullptr, - RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); -} - -void SeasonDialog::RefreshStatus() { - if (SeasonDatabase.items.empty()) return; - - wstring text = SeasonDatabase.name + L", from " + - mal::TranslateSeasonToMonths(SeasonDatabase.name); - - time_t last_modified = 0; - for (auto id = SeasonDatabase.items.begin(); id != SeasonDatabase.items.end(); ++id) { - auto anime_item = AnimeDatabase.FindItem(*id); - if (anime_item) { - if (id == SeasonDatabase.items.begin() || - anime_item->last_modified < last_modified) - last_modified = anime_item->last_modified; - } - } - if (last_modified) { - time_t time_diff = time(nullptr) - last_modified; - text += L" (Last updated: "; - if (time_diff < 60) { - text += L"Now"; - } else if (time_diff > 60 * 60 * 24) { - time_t days = time_diff / (60 * 60 * 24); - text += ToWstr(days) + (days == 1 ? L" day ago" : L" days ago"); - } else { - text += L"Today"; - } - text += L")"; - } - - MainDialog.ChangeStatus(text); -} - -void SeasonDialog::RefreshToolbar() { - toolbar_.EnableButton(101, !SeasonDatabase.items.empty()); - - wstring text = L"Group by: "; - switch (group_by) { - case SEASON_GROUPBY_AIRINGSTATUS: - text += L"Airing status"; - break; - case SEASON_GROUPBY_LISTSTATUS: - text += L"List status"; - break; - case SEASON_GROUPBY_TYPE: - text += L"Type"; - break; - } - toolbar_.SetButtonText(3, text.c_str()); - - text = L"Sort by: "; - switch (sort_by) { - case SEASON_SORTBY_AIRINGDATE: - text += L"Airing date"; - break; - case SEASON_SORTBY_EPISODES: - text += L"Episodes"; - break; - case SEASON_SORTBY_POPULARITY: - text += L"Popularity"; - break; - case SEASON_SORTBY_SCORE: - text += L"Score"; - break; - case SEASON_SORTBY_TITLE: - text += L"Title"; - break; - } - toolbar_.SetButtonText(4, text.c_str()); - - text = L"View: "; - switch (view_as) { - case SEASON_VIEWAS_IMAGES: - text += L"Images"; - break; - case SEASON_VIEWAS_TILES: - text += L"Details"; - break; - } - toolbar_.SetButtonText(5, text.c_str()); -} - -void SeasonDialog::SetViewMode(int mode) { - if (mode == view_as) return; - - SIZE size; - size.cx = mode == SEASON_VIEWAS_IMAGES ? 142 : 500; - size.cy = 200; - list_.SetTileViewInfo(0, LVTVIF_FIXEDSIZE, nullptr, &size); - - view_as = mode; -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "base/gfx.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "library/discover.h" +#include "library/resource.h" +#include "sync/sync.h" +#include "taiga/resource.h" +#include "taiga/script.h" +#include "taiga/settings.h" +#include "ui/dlg/dlg_main.h" +#include "ui/dlg/dlg_season.h" +#include "ui/dialog.h" +#include "ui/list.h" +#include "ui/menu.h" +#include "ui/theme.h" +#include "ui/ui.h" + +namespace ui { + +enum SeasonGroupBy { + kSeasonGroupByAiringStatus, + kSeasonGroupByListStatus, + kSeasonGroupByType +}; + +enum SeasonSortBy { + kSeasonSortByAiringDate, + kSeasonSortByEpisodes, + kSeasonSortByPopularity, + kSeasonSortByScore, + kSeasonSortByTitle +}; + +enum SeasonViewAs { + kSeasonViewAsImages, + kSeasonViewAsTiles +}; + +SeasonDialog DlgSeason; + +SeasonDialog::SeasonDialog() + : group_by(kSeasonGroupByType), + sort_by(kSeasonSortByTitle), + view_as(kSeasonViewAsTiles) { +} + +BOOL SeasonDialog::OnInitDialog() { + // Create list + list_.Attach(GetDlgItem(IDC_LIST_SEASON)); + list_.EnableGroupView(true); + list_.SetExtendedStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT); + list_.SetTheme(); + list_.SetView(LV_VIEW_TILE); + SetViewMode(kSeasonViewAsTiles); + + // Create main toolbar + toolbar_.Attach(GetDlgItem(IDC_TOOLBAR_SEASON)); + toolbar_.SetImageList(ui::Theme.GetImageList16().GetHandle(), 16, 16); + toolbar_.SendMessage(TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS | TBSTYLE_EX_MIXEDBUTTONS); + + // Insert toolbar buttons + BYTE fsState = TBSTATE_ENABLED; + BYTE fsStyle1 = BTNS_AUTOSIZE | BTNS_SHOWTEXT; + BYTE fsStyle2 = BTNS_AUTOSIZE | BTNS_SHOWTEXT | BTNS_WHOLEDROPDOWN; + toolbar_.InsertButton(0, ui::kIcon16_Calendar, 100, fsState, fsStyle2, 0, L"Select season", nullptr); + toolbar_.InsertButton(1, ui::kIcon16_Refresh, 101, fsState, fsStyle1, 1, L"Refresh data", L"Download anime details and missing images"); + toolbar_.InsertButton(2, 0, 0, 0, BTNS_SEP, 0, nullptr, nullptr); + toolbar_.InsertButton(3, ui::kIcon16_Category, 103, fsState, fsStyle2, 3, L"Group by", nullptr); + toolbar_.InsertButton(4, ui::kIcon16_Sort, 104, fsState, fsStyle2, 4, L"Sort by", nullptr); + toolbar_.InsertButton(5, ui::kIcon16_Details, 105, fsState, fsStyle2, 5, L"View", nullptr); + + // Create rebar + rebar_.Attach(GetDlgItem(IDC_REBAR_SEASON)); + // Insert rebar bands + UINT fMask = RBBIM_CHILD | RBBIM_CHILDSIZE | RBBIM_SIZE | RBBIM_STYLE; + UINT fStyle = RBBS_NOGRIPPER; + rebar_.InsertBand(nullptr, 0, 0, 0, 0, 0, 0, 0, 0, fMask, fStyle); + rebar_.InsertBand(toolbar_.GetWindowHandle(), GetSystemMetrics(SM_CXSCREEN), 0, 0, 0, 0, 0, 0, + HIWORD(toolbar_.GetButtonSize()) + (HIWORD(toolbar_.GetPadding()) / 2), fMask, fStyle); + + // Refresh + RefreshList(); + RefreshStatus(); + RefreshToolbar(); + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// + +INT_PTR SeasonDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + // Forward mouse wheel messages to the list + case WM_MOUSEWHEEL: { + return list_.SendMessage(uMsg, wParam, lParam); + } + } + + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + +BOOL SeasonDialog::OnCommand(WPARAM wParam, LPARAM lParam) { + // Toolbar + switch (LOWORD(wParam)) { + // Refresh data + case 101: + RefreshData(); + return TRUE; + } + + return FALSE; +} + +BOOL SeasonDialog::OnDestroy() { + return TRUE; +} + +LRESULT SeasonDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { + // List + if (idCtrl == IDC_LIST_SEASON) { + return OnListNotify(reinterpret_cast(pnmh)); + // Toolbar + } else if (idCtrl == IDC_TOOLBAR_SEASON) { + return OnToolbarNotify(reinterpret_cast(pnmh)); + } + + return 0; +} + +void SeasonDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { + switch (uMsg) { + case WM_SIZE: { + win::Rect rcWindow; + rcWindow.Set(0, 0, size.cx, size.cy); + // Resize rebar + rebar_.SendMessage(WM_SIZE, 0, 0); + rcWindow.top += rebar_.GetBarHeight() + ScaleY(win::kControlMargin / 2); + // Resize list + list_.SetPosition(nullptr, rcWindow); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +LRESULT SeasonDialog::ListView::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_MOUSEWHEEL: { + /* + if (this->GetItemCount() > 0) { + short delta = GET_WHEEL_DELTA_WPARAM(wParam); + short value = 200; + if (delta > 0) value = -value; + SendMessage(LVM_SCROLL, 0, value); + return 0; + } + */ + break; + } + } + + return WindowProcDefault(hwnd, uMsg, wParam, lParam); +} + +LRESULT SeasonDialog::OnListNotify(LPARAM lParam) { + LPNMHDR pnmh = reinterpret_cast(lParam); + switch (pnmh->code) { + // Custom draw + case NM_CUSTOMDRAW: { + return OnListCustomDraw(lParam); + } + + // Double click + case NM_DBLCLK: { + LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); + if (lpnmitem->iItem == -1) + break; + LPARAM param = list_.GetItemParam(lpnmitem->iItem); + if (param) + ShowDlgAnimeInfo(param); + break; + } + + // Right click + case NM_RCLICK: { + LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); + if (lpnmitem->iItem == -1) + break; + auto anime_item = AnimeDatabase.FindItem( + static_cast(list_.GetItemParam(lpnmitem->iItem))); + if (anime_item) { + ui::Menus.UpdateSeasonList(!anime_item->IsInList()); + ExecuteAction(ui::Menus.Show(pnmh->hwndFrom, 0, 0, L"SeasonList"), 0, + static_cast(anime_item->GetId())); + list_.RedrawWindow(); + } + break; + } + } + + return 0; +} + +LRESULT SeasonDialog::OnListCustomDraw(LPARAM lParam) { + LRESULT result = CDRF_DODEFAULT; + LPNMLVCUSTOMDRAW pCD = reinterpret_cast(lParam); + + win::Dc hdc = pCD->nmcd.hdc; + win::Rect rect = pCD->nmcd.rc; + + if (win::GetVersion() < win::kVersionVista) + list_.GetSubItemRect(pCD->nmcd.dwItemSpec, pCD->iSubItem, &rect); + + switch (pCD->nmcd.dwDrawStage) { + case CDDS_PREPAINT: { + // LVN_GETEMPTYMARKUP notification is sent only once, so we paint our own + // markup text when the control has no items. + if (list_.GetItemCount() == 0) { + std::wstring text; + if (SeasonDatabase.items.empty()) { + text = L"No season selected. Please choose one from above."; + } else { + text = L"No matching items for \"" + DlgMain.search_bar.filters.text + L"\"."; + } + hdc.EditFont(L"Segoe UI", 9, -1, TRUE); + hdc.SetBkMode(TRANSPARENT); + hdc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); + hdc.DrawText(text.c_str(), text.length(), rect, + DT_CENTER | DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER); + } + result = CDRF_NOTIFYITEMDRAW; + break; + } + + case CDDS_ITEMPREPAINT: { + result = CDRF_NOTIFYPOSTPAINT; + break; + } + + case CDDS_ITEMPOSTPAINT: { + auto anime_item = AnimeDatabase.FindItem(static_cast(pCD->nmcd.lItemlParam)); + if (!anime_item) + break; + + // Draw border + if (win::GetVersion() > win::kVersionXp) { + rect.Inflate(-4, -4); + } + if (win::GetVersion() < win::kVersionVista && + pCD->nmcd.uItemState & CDIS_SELECTED) { + hdc.FillRect(rect, GetSysColor(COLOR_HIGHLIGHT)); + } else { + hdc.FillRect(rect, ui::kColorGray); + } + + // Draw background + rect.Inflate(-1, -1); + hdc.FillRect(rect, ui::kColorLightGray); + + // Calculate text height + int text_height = GetTextHeight(hdc.Get()); + + // Calculate line count + int line_count = 6; + auto current_service = taiga::GetCurrentServiceId(); + switch (current_service) { + case sync::kMyAnimeList: + line_count = 6; + break; + case sync::kHummingbird: + line_count = 5; + break; + } + + // Calculate areas + win::Rect rect_image( + rect.left + 4, rect.top + 4, + rect.left + 124, rect.bottom - 4); + win::Rect rect_title( + rect_image.right + 4, rect_image.top, + rect.right - 4, rect_image.top + text_height + 8); + win::Rect rect_details( + rect_title.left + 4, rect_title.bottom + 4, + rect_title.right, rect_title.bottom + 4 + (line_count * (text_height + 2))); + win::Rect rect_synopsis( + rect_details.left, rect_details.bottom + 4, + rect_details.right, rect_image.bottom); + + // Draw image + if (ImageDatabase.Load(anime_item->GetId(), false, false)) { + auto image = ImageDatabase.GetImage(anime_item->GetId()); + rect_image = ResizeRect(rect_image, + image->rect.Width(), + image->rect.Height(), + true, true, false); + hdc.SetStretchBltMode(HALFTONE); + hdc.StretchBlt(rect_image.left, rect_image.top, + rect_image.Width(), rect_image.Height(), + image->dc.Get(), 0, 0, + image->rect.Width(), + image->rect.Height(), + SRCCOPY); + } + + // Draw title background + COLORREF color; + switch (anime_item->GetAiringStatus()) { + case anime::kAiring: + color = ui::kColorLightGreen; + break; + case anime::kFinishedAiring: + default: + color = ui::kColorLightBlue; + break; + case anime::kNotYetAired: + color = ui::kColorLightRed; + break; + } + if (view_as == kSeasonViewAsImages) { + rect_title.Copy(rect); + rect_title.top = rect_title.bottom - (text_height + 8); + } + hdc.FillRect(rect_title, color); + + // Draw anime list indicator + if (anime_item->IsInList()) { + ui::Theme.GetImageList16().Draw( + ui::kIcon16_DocumentA, hdc.Get(), + rect_title.right - 20, rect_title.top + 4); + rect_title.right -= 20; + } + + // Set title + std::wstring text = anime_item->GetTitle(); + if (view_as == kSeasonViewAsImages) { + switch (sort_by) { + case kSeasonSortByAiringDate: + text = anime::TranslateDate(anime_item->GetDateStart()); + break; + case kSeasonSortByEpisodes: + text = anime::TranslateNumber(anime_item->GetEpisodeCount(), L""); + if (text.empty()) { + text = L"Unknown"; + } else { + text += text == L"1" ? L" episode" : L" episodes"; + } + break; + case kSeasonSortByPopularity: + text = anime_item->GetPopularity(); + if (text.empty()) + text = L"#0"; + break; + case kSeasonSortByScore: + text = anime_item->GetScore(); + if (InStr(text, L"scored by") > -1) + text = text.substr(0, 4); + if (text.empty()) + text = L"0.00"; + break; + } + } + + // Draw title + rect_title.Inflate(-4, 0); + hdc.EditFont(nullptr, -1, TRUE); + hdc.SetBkMode(TRANSPARENT); + UINT nFormat = DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER; + if (view_as == kSeasonViewAsImages) + nFormat |= DT_CENTER; + hdc.DrawText(text.c_str(), text.length(), rect_title, nFormat); + + // Draw details + if (view_as == kSeasonViewAsImages) + break; + int text_top = rect_details.top; + #define DRAWLINE(t) \ + text = t; \ + hdc.DrawText(text.c_str(), text.length(), rect_details, \ + DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE); \ + rect_details.Offset(0, text_height + 2); + + DRAWLINE(L"Aired:"); + DRAWLINE(L"Episodes:"); + DRAWLINE(L"Genres:"); + DRAWLINE(L"Producers:"); + DRAWLINE(L"Score:"); + if (current_service == sync::kMyAnimeList) { + DRAWLINE(L"Popularity:"); + } + + rect_details.Set(rect_details.left + 75, text_top, + rect_details.right, rect_details.top + text_height); + DeleteObject(hdc.DetachFont()); + + text = anime::TranslateDate(anime_item->GetDateStart()); + text += anime_item->GetDateEnd() != anime_item->GetDateStart() ? + L" to " + anime::TranslateDate(anime_item->GetDateEnd()) : L""; + text += L" (" + anime::TranslateStatus(anime_item->GetAiringStatus()) + L")"; + DRAWLINE(text); + DRAWLINE(anime::TranslateNumber(anime_item->GetEpisodeCount(), L"Unknown")); + DRAWLINE(anime_item->GetGenres().empty() ? L"?" : Join(anime_item->GetGenres(), L", ")); + DRAWLINE(anime_item->GetProducers().empty() ? L"?" : Join(anime_item->GetProducers(), L", ")); + DRAWLINE(anime_item->GetScore().empty() ? L"0.00" : anime_item->GetScore()); + if (current_service == sync::kMyAnimeList) { + DRAWLINE(anime_item->GetPopularity().empty() ? L"#0" : anime_item->GetPopularity()); + } + + #undef DRAWLINE + + // Draw synopsis + if (!anime_item->GetSynopsis().empty()) { + text = anime_item->GetSynopsis(); + // DT_WORDBREAK doesn't go well with DT_*_ELLIPSIS, so we need to make + // sure our text ends with ellipses by clipping that extra pixel. + rect_synopsis.bottom -= (rect_synopsis.Height() % text_height) + 1; + hdc.DrawText(text.c_str(), text.length(), rect_synopsis, + DT_END_ELLIPSIS | DT_NOPREFIX | DT_WORDBREAK); + } + + break; + } + } + + hdc.DetachDc(); + return result; +} + +LRESULT SeasonDialog::OnToolbarNotify(LPARAM lParam) { + switch (reinterpret_cast(lParam)->code) { + // Dropdown button click + case TBN_DROPDOWN: { + RECT rect; + LPNMTOOLBAR nmt = reinterpret_cast(lParam); + ::SendMessage(nmt->hdr.hwndFrom, TB_GETRECT, + static_cast(nmt->iItem), + reinterpret_cast(&rect)); + MapWindowPoints(nmt->hdr.hwndFrom, HWND_DESKTOP, reinterpret_cast(&rect), 2); + ui::Menus.UpdateSeason(); + std::wstring action; + switch (LOWORD(nmt->iItem)) { + // Select season + case 100: + action = ui::Menus.Show(GetWindowHandle(), rect.left, rect.bottom, L"SeasonSelect"); + break; + // Group by + case 103: + action = ui::Menus.Show(GetWindowHandle(), rect.left, rect.bottom, L"SeasonGroup"); + break; + // Sort by + case 104: + action = ui::Menus.Show(GetWindowHandle(), rect.left, rect.bottom, L"SeasonSort"); + break; + // View as + case 105: + action = ui::Menus.Show(GetWindowHandle(), rect.left, rect.bottom, L"SeasonView"); + break; + } + if (!action.empty()) { + ExecuteAction(action); + } + break; + } + + // Show tooltips + case TBN_GETINFOTIP: { + NMTBGETINFOTIP* git = reinterpret_cast(lParam); + git->cchTextMax = INFOTIPSIZE; + if (git->hdr.hwndFrom == toolbar_.GetWindowHandle()) { + git->pszText = const_cast(toolbar_.GetButtonTooltip(git->lParam)); + } + break; + } + } + + return 0L; +} + +//////////////////////////////////////////////////////////////////////////////// + +void SeasonDialog::RefreshData(int anime_id) { + ui::SetSharedCursor(IDC_WAIT); + + foreach_(id, SeasonDatabase.items) { + if (anime_id > 0 && anime_id != *id) + continue; + + auto anime_item = AnimeDatabase.FindItem(*id); + if (!anime_item) + continue; + + // Download missing image + ImageDatabase.Load(*id, true, true); + + // Get details + if (anime::MetadataNeedsRefresh(*anime_item)) + sync::GetMetadataById(*id); + } + + ui::SetSharedCursor(IDC_ARROW); +} + +void SeasonDialog::RefreshList(bool redraw_only) { + if (!IsWindow()) + return; + + if (redraw_only) { + list_.RedrawWindow(); + return; + } + + // Disable drawing + list_.SetRedraw(FALSE); + + // Insert list groups + list_.RemoveAllGroups(); + list_.EnableGroupView(true); // Required for XP + switch (group_by) { + case kSeasonGroupByAiringStatus: + for (int i = anime::kAiring; i <= anime::kNotYetAired; i++) { + list_.InsertGroup(i, anime::TranslateStatus(i).c_str(), true, false); + } + break; + case kSeasonGroupByListStatus: + for (int i = anime::kNotInList; i <= anime::kPlanToWatch; i++) { + list_.InsertGroup(i, anime::TranslateMyStatus(i, false).c_str(), true, false); + } + break; + case kSeasonGroupByType: + for (int i = anime::kTv; i <= anime::kMusic; i++) { + list_.InsertGroup(i, anime::TranslateType(i).c_str(), true, false); + } + break; + } + + // Filter + std::vector filters; + Split(DlgMain.search_bar.filters.text, L" ", filters); + RemoveEmptyStrings(filters); + + // Add items + list_.DeleteAllItems(); + for (auto i = SeasonDatabase.items.begin(); i != SeasonDatabase.items.end(); ++i) { + auto anime_item = AnimeDatabase.FindItem(*i); + bool passed_filters = true; + std::wstring genres = Join(anime_item->GetGenres(), L", "); + std::wstring producers = Join(anime_item->GetProducers(), L", "); + for (auto j = filters.begin(); passed_filters && j != filters.end(); ++j) { + if (InStr(genres, *j, 0, true) == -1 && + InStr(producers, *j, 0, true) == -1 && + InStr(anime_item->GetTitle(), *j, 0, true) == -1) { + passed_filters = false; + break; + } + } + if (!passed_filters) + continue; + int group = -1; + switch (group_by) { + case kSeasonGroupByAiringStatus: + group = anime_item->GetAiringStatus(); + break; + case kSeasonGroupByListStatus: { + group = anime_item->GetMyStatus(); + break; + } + case kSeasonGroupByType: + group = anime_item->GetType(); + break; + } + list_.InsertItem(i - SeasonDatabase.items.begin(), + group, -1, 0, nullptr, LPSTR_TEXTCALLBACK, + static_cast(anime_item->GetId())); + } + + // Sort items + switch (sort_by) { + case kSeasonSortByAiringDate: + list_.Sort(0, -1, ui::kListSortDateStart, ui::ListViewCompareProc); + break; + case kSeasonSortByEpisodes: + list_.Sort(0, -1, ui::kListSortEpisodeCount, ui::ListViewCompareProc); + break; + case kSeasonSortByPopularity: + list_.Sort(0, 1, ui::kListSortPopularity, ui::ListViewCompareProc); + break; + case kSeasonSortByScore: + list_.Sort(0, -1, ui::kListSortScore, ui::ListViewCompareProc); + break; + case kSeasonSortByTitle: + list_.Sort(0, 1, ui::kListSortTitle, ui::ListViewCompareProc); + break; + } + + // Redraw + list_.SetRedraw(TRUE); + list_.RedrawWindow(nullptr, nullptr, + RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); +} + +void SeasonDialog::RefreshStatus() { + if (SeasonDatabase.items.empty()) + return; + + std::wstring text = SeasonDatabase.name + L", from " + + anime::TranslateSeasonToMonths(SeasonDatabase.name); + + ui::ChangeStatusText(text); +} + +void SeasonDialog::RefreshToolbar() { + toolbar_.EnableButton(101, !SeasonDatabase.items.empty()); + + std::wstring text = L"Group by: "; + switch (group_by) { + case kSeasonGroupByAiringStatus: + text += L"Airing status"; + break; + case kSeasonGroupByListStatus: + text += L"List status"; + break; + case kSeasonGroupByType: + text += L"Type"; + break; + } + toolbar_.SetButtonText(3, text.c_str()); + + text = L"Sort by: "; + switch (sort_by) { + case kSeasonSortByAiringDate: + text += L"Airing date"; + break; + case kSeasonSortByEpisodes: + text += L"Episodes"; + break; + case kSeasonSortByPopularity: + text += L"Popularity"; + break; + case kSeasonSortByScore: + text += L"Score"; + break; + case kSeasonSortByTitle: + text += L"Title"; + break; + } + toolbar_.SetButtonText(4, text.c_str()); + + text = L"View: "; + switch (view_as) { + case kSeasonViewAsImages: + text += L"Images"; + break; + case kSeasonViewAsTiles: + text += L"Details"; + break; + } + toolbar_.SetButtonText(5, text.c_str()); +} + +void SeasonDialog::SetViewMode(int mode) { + SIZE size; + size.cx = mode == kSeasonViewAsImages ? 142 : 500; + size.cy = 200; + list_.SetTileViewInfo(0, LVTVIF_FIXEDSIZE, nullptr, &size); + + view_as = mode; +} + +} // namespace ui diff --git a/dlg/dlg_season.h b/src/ui/dlg/dlg_season.h similarity index 57% rename from dlg/dlg_season.h rename to src/ui/dlg/dlg_season.h index 9b1e9121f..e602baf3f 100644 --- a/dlg/dlg_season.h +++ b/src/ui/dlg/dlg_season.h @@ -1,85 +1,66 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_SEASON_H -#define DLG_SEASON_H - -#include "../std.h" - -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" - -// ============================================================================= - -enum SeasonGroupBy { - SEASON_GROUPBY_AIRINGSTATUS, - SEASON_GROUPBY_LISTSTATUS, - SEASON_GROUPBY_TYPE -}; - -enum SeasonSortBy { - SEASON_SORTBY_AIRINGDATE, - SEASON_SORTBY_EPISODES, - SEASON_SORTBY_POPULARITY, - SEASON_SORTBY_SCORE, - SEASON_SORTBY_TITLE -}; - -enum SeasonViewAs { - SEASON_VIEWAS_IMAGES, - SEASON_VIEWAS_TILES -}; - -class SeasonDialog : public win32::Dialog { -public: - SeasonDialog(); - virtual ~SeasonDialog() {} - - INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - BOOL OnCommand(WPARAM wParam, LPARAM lParam); - BOOL OnDestroy(); - BOOL OnInitDialog(); - LRESULT OnListCustomDraw(LPARAM lParam); - LRESULT OnListNotify(LPARAM lParam); - LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); - void OnSize(UINT uMsg, UINT nType, SIZE size); - LRESULT OnToolbarNotify(LPARAM lParam); - -public: - void RefreshData(int anime_id = 0); - void RefreshList(bool redraw_only = false); - void RefreshStatus(); - void RefreshToolbar(); - void SetViewMode(int mode); - -public: - int group_by, sort_by, view_as; - -private: - win32::Window cancel_button_; - class ListView : public win32::ListView { - public: - LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - } list_; - win32::Rebar rebar_; - win32::Toolbar toolbar_; -}; - -extern class SeasonDialog SeasonDialog; - -#endif // DLG_SEASON_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_SEASON_H +#define TAIGA_UI_DLG_SEASON_H + +#include "win/ctrl/win_ctrl.h" +#include "win/win_dialog.h" + +namespace ui { + +class SeasonDialog : public win::Dialog { +public: + SeasonDialog(); + ~SeasonDialog() {} + + INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + BOOL OnCommand(WPARAM wParam, LPARAM lParam); + BOOL OnDestroy(); + BOOL OnInitDialog(); + LRESULT OnListCustomDraw(LPARAM lParam); + LRESULT OnListNotify(LPARAM lParam); + LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); + void OnSize(UINT uMsg, UINT nType, SIZE size); + LRESULT OnToolbarNotify(LPARAM lParam); + + void RefreshData(int anime_id = 0); + void RefreshList(bool redraw_only = false); + void RefreshStatus(); + void RefreshToolbar(); + void SetViewMode(int mode); + + int group_by; + int sort_by; + int view_as; + +private: + win::Window cancel_button_; + class ListView : public win::ListView { + public: + LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + } list_; + win::Rebar rebar_; + win::Toolbar toolbar_; +}; + +extern SeasonDialog DlgSeason; + +} // namespace ui + +#endif // TAIGA_UI_DLG_SEASON_H \ No newline at end of file diff --git a/src/ui/dlg/dlg_settings.cpp b/src/ui/dlg/dlg_settings.cpp new file mode 100644 index 000000000..9d1280c6d --- /dev/null +++ b/src/ui/dlg/dlg_settings.cpp @@ -0,0 +1,556 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/crypto.h" +#include "base/file.h" +#include "base/foreach.h" +#include "base/string.h" +#include "library/history.h" +#include "sync/manager.h" +#include "taiga/resource.h" +#include "taiga/settings.h" +#include "taiga/stats.h" +#include "taiga/taiga.h" +#include "track/media.h" +#include "track/monitor.h" +#include "ui/dlg/dlg_settings.h" +#include "ui/theme.h" +#include "win/win_taskdialog.h" + +namespace ui { + +const WCHAR* kSectionTitles[] = { + L" Services", + L" Library", + L" Application", + L" Recognition", + L" Sharing", + L" Torrents" +}; + +SettingsDialog DlgSettings; + +SettingsDialog::SettingsDialog() + : current_section_(kSettingsSectionServices), + current_page_(kSettingsPageServicesMain) { + RegisterDlgClass(L"TaigaSettingsW"); + + pages.resize(kSettingsPageCount); + for (size_t i = 0; i < kSettingsPageCount; i++) { + pages[i].index = i; + pages[i].parent = this; + } +} + +void SettingsDialog::SetCurrentSection(SettingsSections section) { + current_section_ = section; + + if (!IsWindow()) + return; + + SetDlgItemText(IDC_STATIC_TITLE, kSectionTitles[current_section_ - 1]); + + tab_.DeleteAllItems(); + switch (current_section_) { + case kSettingsSectionServices: + tab_.InsertItem(0, L"Main", kSettingsPageServicesMain); + tab_.InsertItem(1, L"MyAnimeList", kSettingsPageServicesMal); + tab_.InsertItem(2, L"Hummingbird", kSettingsPageServicesHummingbird); + break; + case kSettingsSectionLibrary: + tab_.InsertItem(0, L"Folders", kSettingsPageLibraryFolders); + tab_.InsertItem(1, L"Cache", kSettingsPageLibraryCache); + break; + case kSettingsSectionApplication: + tab_.InsertItem(0, L"Anime list", kSettingsPageAppList); + tab_.InsertItem(1, L"Behavior", kSettingsPageAppBehavior); + tab_.InsertItem(2, L"Connection", kSettingsPageAppConnection); + tab_.InsertItem(3, L"Interface", kSettingsPageAppInterface); + break; + case kSettingsSectionRecognition: + tab_.InsertItem(0, L"General", kSettingsPageRecognitionGeneral); + tab_.InsertItem(1, L"Media players", kSettingsPageRecognitionMedia); + tab_.InsertItem(2, L"Media providers", kSettingsPageRecognitionStream); + break; + case kSettingsSectionSharing: + tab_.InsertItem(0, L"HTTP", kSettingsPageSharingHttp); + tab_.InsertItem(1, L"mIRC", kSettingsPageSharingMirc); + tab_.InsertItem(2, L"Skype", kSettingsPageSharingSkype); + tab_.InsertItem(3, L"Twitter", kSettingsPageSharingTwitter); + break; + case kSettingsSectionTorrents: + tab_.InsertItem(0, L"Discovery", kSettingsPageTorrentsDiscovery); + tab_.InsertItem(1, L"Downloads", kSettingsPageTorrentsDownloads); + tab_.InsertItem(2, L"Filters", kSettingsPageTorrentsFilters); + break; + } +} + +void SettingsDialog::SetCurrentPage(SettingsPages page) { + pages.at(current_page_).Hide(); + + current_page_ = page; + + if (!IsWindow()) + return; + + if (!pages.at(current_page_).IsWindow()) + pages.at(current_page_).Create(); + pages.at(current_page_).Show(); + + for (int i = 0; i < tab_.GetItemCount(); i++) { + if (tab_.GetItemParam(i) == current_page_) { + tab_.SetCurrentlySelected(i); + break; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL SettingsDialog::OnInitDialog() { + // Initialize controls + tree_.Attach(GetDlgItem(IDC_TREE_SECTIONS)); + tree_.SetImageList(ui::Theme.GetImageList24().GetHandle()); + tree_.SetTheme(); + tab_.Attach(GetDlgItem(IDC_TAB_PAGES)); + + // Add tree items + #define TREE_INSERTITEM(s, t, i) \ + tree_.items[s] = tree_.InsertItem(t, i, s, nullptr); + TREE_INSERTITEM(kSettingsSectionServices, L"Services", ui::kIcon24_Globe); + TREE_INSERTITEM(kSettingsSectionLibrary, L"Library", ui::kIcon24_Library); + TREE_INSERTITEM(kSettingsSectionApplication, L"Application", ui::kIcon24_Application); + TREE_INSERTITEM(kSettingsSectionRecognition, L"Recognition", ui::kIcon24_Recognition); + TREE_INSERTITEM(kSettingsSectionSharing, L"Sharing", ui::kIcon24_Sharing); + TREE_INSERTITEM(kSettingsSectionTorrents, L"Torrents", ui::kIcon24_Feed); + #undef TREE_INSERTITEM + + // Set title font + SendDlgItemMessage(IDC_STATIC_TITLE, WM_SETFONT, + reinterpret_cast(ui::Theme.GetBoldFont()), TRUE); + + // Select current section and page + SettingsPages current_page = current_page_; + tree_.SelectItem(tree_.items[current_section_]); + SetCurrentSection(current_section_); + SetCurrentPage(current_page); + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// + +void SettingsDialog::OnOK() { + win::ListView list; + SettingsPage* page = nullptr; + + std::wstring previous_service = taiga::GetCurrentService()->canonical_name(); + std::wstring previous_user = taiga::GetCurrentUsername(); + std::wstring previous_theme = Settings[taiga::kApp_Interface_Theme]; + + // Services > Main + page = &pages[kSettingsPageServicesMain]; + if (page->IsWindow()) { + win::ComboBox combo(page->GetDlgItem(IDC_COMBO_SERVICE)); + auto service_id = static_cast(combo.GetItemData(combo.GetCurSel())); + auto service = ServiceManager.service(service_id); + Settings.Set(taiga::kSync_ActiveService, service->canonical_name()); + Settings.Set(taiga::kSync_AutoOnStart, page->IsDlgButtonChecked(IDC_CHECK_START_LOGIN)); + combo.SetWindowHandle(nullptr); + } + // Services > MyAnimeList + page = &pages[kSettingsPageServicesMal]; + if (page->IsWindow()) { + Settings.Set(taiga::kSync_Service_Mal_Username, page->GetDlgItemText(IDC_EDIT_USER_MAL)); + Settings.Set(taiga::kSync_Service_Mal_Password, SimpleEncrypt(page->GetDlgItemText(IDC_EDIT_PASS_MAL))); + } + // Services > Hummingbird + page = &pages[kSettingsPageServicesHummingbird]; + if (page->IsWindow()) { + Settings.Set(taiga::kSync_Service_Hummingbird_Username, page->GetDlgItemText(IDC_EDIT_USER_HUMMINGBIRD)); + Settings.Set(taiga::kSync_Service_Hummingbird_Password, SimpleEncrypt(page->GetDlgItemText(IDC_EDIT_PASS_HUMMINGBIRD))); + } + + // Library > Folders + page = &pages[kSettingsPageLibraryFolders]; + if (page->IsWindow()) { + list.SetWindowHandle(page->GetDlgItem(IDC_LIST_FOLDERS_ROOT)); + Settings.root_folders.clear(); + for (int i = 0; i < list.GetItemCount(); i++) { + std::wstring folder; + list.GetItemText(i, 0, folder); + Settings.root_folders.push_back(folder); + } + Settings.Set(taiga::kLibrary_WatchFolders, page->IsDlgButtonChecked(IDC_CHECK_FOLDERS_WATCH)); + list.SetWindowHandle(nullptr); + } + + // Application > Behavior + page = &pages[kSettingsPageAppBehavior]; + if (page->IsWindow()) { + Settings.Set(taiga::kApp_Behavior_Autostart, page->IsDlgButtonChecked(IDC_CHECK_AUTOSTART)); + Settings.Set(taiga::kApp_Behavior_CloseToTray, page->IsDlgButtonChecked(IDC_CHECK_GENERAL_CLOSE)); + Settings.Set(taiga::kApp_Behavior_MinimizeToTray, page->IsDlgButtonChecked(IDC_CHECK_GENERAL_MINIMIZE)); + Settings.Set(taiga::kApp_Behavior_CheckForUpdates, page->IsDlgButtonChecked(IDC_CHECK_START_VERSION)); + Settings.Set(taiga::kApp_Behavior_ScanAvailableEpisodes, page->IsDlgButtonChecked(IDC_CHECK_START_CHECKEPS)); + Settings.Set(taiga::kApp_Behavior_StartMinimized, page->IsDlgButtonChecked(IDC_CHECK_START_MINIMIZE)); + } + // Application > Connection + page = &pages[kSettingsPageAppConnection]; + if (page->IsWindow()) { + Settings.Set(taiga::kApp_Connection_ProxyHost, page->GetDlgItemText(IDC_EDIT_PROXY_HOST)); + Settings.Set(taiga::kApp_Connection_ProxyUsername, page->GetDlgItemText(IDC_EDIT_PROXY_USER)); + Settings.Set(taiga::kApp_Connection_ProxyPassword, page->GetDlgItemText(IDC_EDIT_PROXY_PASS)); + } + // Application > Interface + page = &pages[kSettingsPageAppInterface]; + if (page->IsWindow()) { + Settings.Set(taiga::kApp_Interface_Theme, page->GetDlgItemText(IDC_COMBO_THEME)); + Settings.Set(taiga::kApp_Interface_ExternalLinks, page->GetDlgItemText(IDC_EDIT_EXTERNALLINKS)); + } + // Application > List + page = &pages[kSettingsPageAppList]; + if (page->IsWindow()) { + Settings.Set(taiga::kApp_List_DoubleClickAction, page->GetComboSelection(IDC_COMBO_DBLCLICK)); + Settings.Set(taiga::kApp_List_MiddleClickAction, page->GetComboSelection(IDC_COMBO_MDLCLICK)); + Settings.Set(taiga::kApp_List_DisplayEnglishTitles, page->IsDlgButtonChecked(IDC_CHECK_LIST_ENGLISH)); + Settings.Set(taiga::kApp_List_HighlightNewEpisodes, page->IsDlgButtonChecked(IDC_CHECK_HIGHLIGHT)); + Settings.Set(taiga::kApp_List_ProgressDisplayAired, page->IsDlgButtonChecked(IDC_CHECK_LIST_PROGRESS_AIRED)); + Settings.Set(taiga::kApp_List_ProgressDisplayAvailable, page->IsDlgButtonChecked(IDC_CHECK_LIST_PROGRESS_AVAILABLE)); + } + + // Recognition > General + page = &pages[kSettingsPageRecognitionGeneral]; + if (page->IsWindow()) { + Settings.Set(taiga::kSync_Update_AskToConfirm, page->IsDlgButtonChecked(IDC_CHECK_UPDATE_CONFIRM)); + Settings.Set(taiga::kSync_Update_CheckPlayer, page->IsDlgButtonChecked(IDC_CHECK_UPDATE_CHECKMP)); + Settings.Set(taiga::kSync_Update_GoToNowPlaying, page->IsDlgButtonChecked(IDC_CHECK_UPDATE_GOTO)); + Settings.Set(taiga::kSync_Update_OutOfRange, page->IsDlgButtonChecked(IDC_CHECK_UPDATE_RANGE)); + Settings.Set(taiga::kSync_Update_OutOfRoot, page->IsDlgButtonChecked(IDC_CHECK_UPDATE_ROOT)); + Settings.Set(taiga::kSync_Update_WaitPlayer, page->IsDlgButtonChecked(IDC_CHECK_UPDATE_WAITMP)); + Settings.Set(taiga::kSync_Update_Delay, static_cast(page->GetDlgItemInt(IDC_EDIT_DELAY))); + Settings.Set(taiga::kSync_Notify_Recognized, page->IsDlgButtonChecked(IDC_CHECK_NOTIFY_RECOGNIZED)); + Settings.Set(taiga::kSync_Notify_NotRecognized, page->IsDlgButtonChecked(IDC_CHECK_NOTIFY_NOTRECOGNIZED)); + } + // Recognition > Media players + page = &pages[kSettingsPageRecognitionMedia]; + if (page->IsWindow()) { + list.SetWindowHandle(page->GetDlgItem(IDC_LIST_MEDIA)); + for (size_t i = 0; i < MediaPlayers.items.size(); i++) + MediaPlayers.items[i].enabled = list.GetCheckState(i); + list.SetWindowHandle(nullptr); + } + // Recognition > Media providers + page = &pages[kSettingsPageRecognitionStream]; + if (page->IsWindow()) { + list.SetWindowHandle(page->GetDlgItem(IDC_LIST_STREAM_PROVIDER)); + Settings.Set(taiga::kStream_Ann, list.GetCheckState(0) == TRUE); + Settings.Set(taiga::kStream_Crunchyroll, list.GetCheckState(1) == TRUE); + Settings.Set(taiga::kStream_Veoh, list.GetCheckState(2) == TRUE); + Settings.Set(taiga::kStream_Viz, list.GetCheckState(3) == TRUE); + Settings.Set(taiga::kStream_Youtube, list.GetCheckState(4) == TRUE); + list.SetWindowHandle(nullptr); + } + + // Sharing > HTTP + page = &pages[kSettingsPageSharingHttp]; + if (page->IsWindow()) { + Settings.Set(taiga::kShare_Http_Enabled, page->IsDlgButtonChecked(IDC_CHECK_HTTP)); + Settings.Set(taiga::kShare_Http_Url, page->GetDlgItemText(IDC_EDIT_HTTP_URL)); + } + // Sharing > mIRC + page = &pages[kSettingsPageSharingMirc]; + if (page->IsWindow()) { + Settings.Set(taiga::kShare_Mirc_Enabled, page->IsDlgButtonChecked(IDC_CHECK_MIRC)); + Settings.Set(taiga::kShare_Mirc_Service, page->GetDlgItemText(IDC_EDIT_MIRC_SERVICE)); + Settings.Set(taiga::kShare_Mirc_Mode, page->GetCheckedRadioButton(IDC_RADIO_MIRC_CHANNEL1, IDC_RADIO_MIRC_CHANNEL3) + 1); + Settings.Set(taiga::kShare_Mirc_MultiServer, page->IsDlgButtonChecked(IDC_CHECK_MIRC_MULTISERVER)); + Settings.Set(taiga::kShare_Mirc_UseMeAction, page->IsDlgButtonChecked(IDC_CHECK_MIRC_ACTION)); + Settings.Set(taiga::kShare_Mirc_Channels, page->GetDlgItemText(IDC_EDIT_MIRC_CHANNELS)); + } + // Sharing > Skype + page = &pages[kSettingsPageSharingSkype]; + if (page->IsWindow()) { + Settings.Set(taiga::kShare_Skype_Enabled, page->IsDlgButtonChecked(IDC_CHECK_SKYPE)); + } + // Sharing > Twitter + page = &pages[kSettingsPageSharingTwitter]; + if (page->IsWindow()) { + Settings.Set(taiga::kShare_Twitter_Enabled, page->IsDlgButtonChecked(IDC_CHECK_TWITTER)); + } + + // Torrents > Discovery + page = &pages[kSettingsPageTorrentsDiscovery]; + if (page->IsWindow()) { + Settings.Set(taiga::kTorrent_Discovery_Source, page->GetDlgItemText(IDC_COMBO_TORRENT_SOURCE)); + Settings.Set(taiga::kTorrent_Discovery_SearchUrl, page->GetDlgItemText(IDC_COMBO_TORRENT_SEARCH)); + Settings.Set(taiga::kTorrent_Discovery_AutoCheckEnabled, page->IsDlgButtonChecked(IDC_CHECK_TORRENT_AUTOCHECK)); + Settings.Set(taiga::kTorrent_Discovery_AutoCheckInterval, static_cast(page->GetDlgItemInt(IDC_EDIT_TORRENT_INTERVAL))); + Settings.Set(taiga::kTorrent_Discovery_NewAction, page->GetCheckedRadioButton(IDC_RADIO_TORRENT_NEW1, IDC_RADIO_TORRENT_NEW2) + 1); + } + // Torrents > Downloads + page = &pages[kSettingsPageTorrentsDownloads]; + if (page->IsWindow()) { + Settings.Set(taiga::kTorrent_Download_AppMode, page->GetCheckedRadioButton(IDC_RADIO_TORRENT_APP1, IDC_RADIO_TORRENT_APP2) + 1); + Settings.Set(taiga::kTorrent_Download_AppPath, page->GetDlgItemText(IDC_EDIT_TORRENT_APP)); + Settings.Set(taiga::kTorrent_Download_UseAnimeFolder, page->IsDlgButtonChecked(IDC_CHECK_TORRENT_AUTOSETFOLDER)); + Settings.Set(taiga::kTorrent_Download_FallbackOnFolder, page->IsDlgButtonChecked(IDC_CHECK_TORRENT_AUTOUSEFOLDER)); + Settings.Set(taiga::kTorrent_Download_Location, page->GetDlgItemText(IDC_COMBO_TORRENT_FOLDER)); + Settings.Set(taiga::kTorrent_Download_CreateSubfolder, page->IsDlgButtonChecked(IDC_CHECK_TORRENT_AUTOCREATEFOLDER)); + } + // Torrents > Filters + page = &pages[kSettingsPageTorrentsFilters]; + if (page->IsWindow()) { + Settings.Set(taiga::kTorrent_Filter_Enabled, page->IsDlgButtonChecked(IDC_CHECK_TORRENT_FILTER)); + list.SetWindowHandle(page->GetDlgItem(IDC_LIST_TORRENT_FILTER)); + for (int i = 0; i < list.GetItemCount(); i++) { + FeedFilter* filter = reinterpret_cast(list.GetItemParam(i)); + if (filter) filter->enabled = list.GetCheckState(i) == TRUE; + } + list.SetWindowHandle(nullptr); + Aggregator.filter_manager.filters.clear(); + for (auto it = feed_filters_.begin(); it != feed_filters_.end(); ++it) + Aggregator.filter_manager.filters.push_back(*it); + } + + // Save settings + Settings.Save(); + + // Apply changes + Settings.ApplyChanges(previous_service, previous_user, previous_theme); + + // End dialog + EndDialog(IDOK); +} + +//////////////////////////////////////////////////////////////////////////////// + +INT_PTR SettingsDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + // Set title color + case WM_CTLCOLORSTATIC: { + HDC hdc = reinterpret_cast(wParam); + HWND hwnd_static = reinterpret_cast(lParam); + if (hwnd_static == GetDlgItem(IDC_STATIC_TITLE)) { + ::SetBkMode(hdc, TRANSPARENT); + ::SetTextColor(hdc, ::GetSysColor(COLOR_WINDOW)); + return reinterpret_cast(::GetSysColorBrush(COLOR_APPWORKSPACE)); + } + break; + } + + // Drag window + case WM_ENTERSIZEMOVE: + if (::IsAppThemed() && win::GetVersion() >= win::kVersionVista) + SetTransparency(200); + break; + case WM_EXITSIZEMOVE: + if (::IsAppThemed() && win::GetVersion() >= win::kVersionVista) + SetTransparency(255); + break; + + // Taiga, help! Only you can save us! + case WM_HELP: { + OnHelp(reinterpret_cast(lParam)); + return TRUE; + } + + // Forward mouse wheel messages to the list + case WM_MOUSEWHEEL: + switch (current_page_) { + case kSettingsPageLibraryFolders: + return pages[kSettingsPageLibraryFolders].SendDlgItemMessage( + IDC_LIST_FOLDERS_ROOT, uMsg, wParam, lParam); + case kSettingsPageRecognitionMedia: + return pages[kSettingsPageRecognitionMedia].SendDlgItemMessage( + IDC_LIST_MEDIA, uMsg, wParam, lParam); + case kSettingsPageTorrentsFilters: + return pages[kSettingsPageTorrentsFilters].SendDlgItemMessage( + IDC_LIST_TORRENT_FILTER, uMsg, wParam, lParam); + } + break; + } + + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + +void SettingsDialog::OnHelp(LPHELPINFO lphi) { + std::wstring message, text; + pages.at(current_page_).GetDlgItemText(lphi->iCtrlId, text); + + #define SET_HELP_TEXT(i, m) \ + case i: message = m; break; + switch (lphi->iCtrlId) { + // Library > Folders + SET_HELP_TEXT(IDC_LIST_FOLDERS_ROOT, + L"These folders will be scanned and monitored for new episodes.\n\n" + L"Suppose that you have an HDD like this:\n\n" + L" D:\\\n" + L" \u2514 Anime\n" + L" \u2514 Bleach\n" + L" \u2514 Naruto\n" + L" \u2514 One Piece\n" + L" \u2514 Games\n" + L" \u2514 Music\n\n" + L"In this case, \"D:\\Anime\" is the root folder you should add."); + SET_HELP_TEXT(IDC_CHECK_FOLDERS_WATCH, + L"With this feature on, Taiga instantly detects when a file is added, removed, or renamed under root folders and their subfolders.\n\n" + L"Enabling this feature is recommended."); + // Not available + default: + message = L"There's no help message associated with this item."; + break; + } + #undef SET_HELP_TEXT + + MessageBox(message.c_str(), L"Help", MB_ICONINFORMATION | MB_OK); +} + +LRESULT SettingsDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { + switch (idCtrl) { + case IDC_TREE_SECTIONS: { + switch (pnmh->code) { + // Select section + case TVN_SELCHANGED: { + LPNMTREEVIEW pnmtv = reinterpret_cast(pnmh); + auto section_new = static_cast(pnmtv->itemNew.lParam); + auto section_old = static_cast(pnmtv->itemOld.lParam); + if (section_new != section_old) { + SetCurrentSection(section_new); + SetCurrentPage(static_cast(tab_.GetItemParam(0))); + } + break; + } + } + break; + } + + case IDC_TAB_PAGES: { + switch (pnmh->code) { + // Select tab + case TCN_SELCHANGE: { + auto page = static_cast(tab_.GetItemParam(tab_.GetCurrentlySelected())); + SetCurrentPage(page); + break; + } + } + break; + } + + case IDC_LINK_DEFAULTS: { + switch (pnmh->code) { + // Restore default settings + case NM_CLICK: { + win::TaskDialog dlg; + dlg.SetWindowTitle(TAIGA_APP_NAME); + dlg.SetMainIcon(TD_ICON_WARNING); + dlg.SetMainInstruction(L"Are you sure you want to restore default settings?"); + dlg.SetContent(L"All your current settings will be lost."); + dlg.AddButton(L"Yes", IDYES); + dlg.AddButton(L"No", IDNO); + dlg.Show(GetWindowHandle()); + if (dlg.GetSelectedButtonID() == IDYES) + Settings.RestoreDefaults(); + return TRUE; + } + } + break; + } + } + + return 0; +} + +LRESULT SettingsDialog::TreeView::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + // Forward mouse wheel messages to parent + case WM_MOUSEWHEEL: + return ::SendMessage(GetParent(), uMsg, wParam, lParam); + } + + return WindowProcDefault(hwnd, uMsg, wParam, lParam); +} + +//////////////////////////////////////////////////////////////////////////////// + +int SettingsDialog::AddTorrentFilterToList(HWND hwnd_list, const FeedFilter& filter) { + win::ListView list = hwnd_list; + int index = list.GetItemCount(); + int group = filter.anime_ids.empty() ? 0 : 1; + + int icon = ui::kIcon16_Funnel; + switch (filter.action) { + case kFeedFilterActionDiscard: icon = ui::kIcon16_FunnelCross; break; + case kFeedFilterActionSelect: icon = ui::kIcon16_FunnelTick; break; + case kFeedFilterActionPrefer: icon = ui::kIcon16_FunnelPlus; break; + } + + // Insert item + index = list.InsertItem(index, group, icon, 0, nullptr, filter.name.c_str(), + reinterpret_cast(&filter)); + list.SetCheckState(index, filter.enabled); + list.SetWindowHandle(nullptr); + + return index; +} + +void SettingsDialog::RefreshCache() { + std::wstring text; + Stats.CalculateLocalData(); + SettingsPage& page = pages[kSettingsPageLibraryCache]; + + // History + text = ToWstr(static_cast(History.items.size())) + L" item(s)"; + page.SetDlgItemText(IDC_STATIC_CACHE1, text.c_str()); + + // Image files + text = ToWstr(Stats.image_count) + L" item(s), " + ToSizeString(Stats.image_size); + page.SetDlgItemText(IDC_STATIC_CACHE2, text.c_str()); + + // Torrent files + text = ToWstr(Stats.torrent_count) + L" item(s), " + ToSizeString(Stats.torrent_size); + page.SetDlgItemText(IDC_STATIC_CACHE3, text.c_str()); +} + +void SettingsDialog::RefreshTorrentFilterList(HWND hwnd_list) { + win::ListView list = hwnd_list; + list.DeleteAllItems(); + + for (auto it = feed_filters_.begin(); it != feed_filters_.end(); ++it) + AddTorrentFilterToList(hwnd_list, *it); + + list.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); + list.SetWindowHandle(nullptr); +} + +void SettingsDialog::RefreshTwitterLink() { + std::wstring text; + + if (Settings[taiga::kShare_Twitter_Username].empty()) { + text = L"Taiga is not authorized to post to your Twitter account yet."; + } else { + text = L"Taiga is authorized to post to this Twitter account: "; + text += L"" + Settings[taiga::kShare_Twitter_Username] + L""; + } + + pages[kSettingsPageSharingTwitter].SetDlgItemText(IDC_LINK_TWITTER, text.c_str()); +} + +} // namespace ui \ No newline at end of file diff --git a/dlg/dlg_settings.h b/src/ui/dlg/dlg_settings.h similarity index 57% rename from dlg/dlg_settings.h rename to src/ui/dlg/dlg_settings.h index 69a4d46e5..7c87788e6 100644 --- a/dlg/dlg_settings.h +++ b/src/ui/dlg/dlg_settings.h @@ -1,76 +1,81 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_SETTINGS_H -#define DLG_SETTINGS_H - -#include "../std.h" -#include "dlg_settings_page.h" -#include "../feed.h" -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" - -enum SettingsSections { - SECTION_SERVICES = 1, - SECTION_LIBRARY, - SECTION_APPLICATION, - SECTION_RECOGNITION, - SECTION_SHARING, - SECTION_TORRENTS -}; -// ============================================================================= - -class SettingsDialog : public win32::Dialog { -public: - SettingsDialog(); - virtual ~SettingsDialog() {} - - friend class SettingsPage; - - INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - void OnHelp(LPHELPINFO lphi); - BOOL OnInitDialog(); - LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); - void OnOK(); - - void SetCurrentSection(int index); - void SetCurrentPage(int index); - - int AddTorrentFilterToList(HWND hwnd_list, const FeedFilter& filter); - void RefreshCache(); - void RefreshTorrentFilterList(HWND hwnd_list); - void RefreshTwitterLink(); - -private: - class TreeView : public win32::TreeView { - public: - LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - std::map items; - } tree_; - win32::Tab tab_; - -private: - int current_section_; - int current_page_; - vector feed_filters_; - vector pages; -}; - -extern class SettingsDialog SettingsDialog; - -#endif // DLG_SETTINGS_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_SETTINGS_H +#define TAIGA_UI_DLG_SETTINGS_H + +#include +#include + +#include "track/feed.h" +#include "win/ctrl/win_ctrl.h" +#include "win/win_dialog.h" +#include "ui/dlg/dlg_settings_page.h" + +namespace ui { + +enum SettingsSections { + kSettingsSectionServices = 1, + kSettingsSectionLibrary, + kSettingsSectionApplication, + kSettingsSectionRecognition, + kSettingsSectionSharing, + kSettingsSectionTorrents +}; + +class SettingsDialog : public win::Dialog { +public: + SettingsDialog(); + ~SettingsDialog() {} + + friend class SettingsPage; + + INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + void OnHelp(LPHELPINFO lphi); + BOOL OnInitDialog(); + LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); + void OnOK(); + + void SetCurrentSection(SettingsSections section); + void SetCurrentPage(SettingsPages page); + + int AddTorrentFilterToList(HWND hwnd_list, const FeedFilter& filter); + void RefreshCache(); + void RefreshTorrentFilterList(HWND hwnd_list); + void RefreshTwitterLink(); + +private: + class TreeView : public win::TreeView { + public: + LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + std::map items; + } tree_; + win::Tab tab_; + +private: + SettingsSections current_section_; + SettingsPages current_page_; + std::vector feed_filters_; + std::vector pages; +}; + +extern SettingsDialog DlgSettings; + +} // namespace ui + +#endif // TAIGA_UI_DLG_SETTINGS_H \ No newline at end of file diff --git a/dlg/dlg_settings_page.cpp b/src/ui/dlg/dlg_settings_page.cpp similarity index 52% rename from dlg/dlg_settings_page.cpp rename to src/ui/dlg/dlg_settings_page.cpp index d6469e053..9bd3ff7a1 100644 --- a/dlg/dlg_settings_page.cpp +++ b/src/ui/dlg/dlg_settings_page.cpp @@ -1,782 +1,800 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_format.h" -#include "dlg_history.h" -#include "dlg_settings.h" -#include "dlg_settings_page.h" -#include "dlg_feed_filter.h" - -#include "../anime_db.h" -#include "../anime_filter.h" -#include "../announce.h" -#include "../common.h" -#include "../history.h" -#include "../http.h" -#include "../media.h" -#include "../myanimelist.h" -#include "../resource.h" -#include "../settings.h" -#include "../stats.h" -#include "../string.h" -#include "../taiga.h" -#include "../theme.h" - -// ============================================================================= - -SettingsPage::SettingsPage() - : parent(nullptr) { -} - -void SettingsPage::Create() { - win32::Rect rect_page; - parent->tab_.GetWindowRect(parent->GetWindowHandle(), &rect_page); - parent->tab_.AdjustRect(nullptr, FALSE, &rect_page); - - UINT resource_id = 0; - switch (index) { - #define SETRESOURCEID(page, id) case page: resource_id = id; break; - SETRESOURCEID(PAGE_APP_BEHAVIOR, IDD_SETTINGS_APP_BEHAVIOR); - SETRESOURCEID(PAGE_APP_CONNECTION, IDD_SETTINGS_APP_CONNECTION); - SETRESOURCEID(PAGE_APP_INTERFACE, IDD_SETTINGS_APP_INTERFACE); - SETRESOURCEID(PAGE_APP_LIST, IDD_SETTINGS_APP_LIST); - SETRESOURCEID(PAGE_LIBRARY_FOLDERS, IDD_SETTINGS_LIBRARY_FOLDERS); - SETRESOURCEID(PAGE_LIBRARY_CACHE, IDD_SETTINGS_LIBRARY_CACHE); - SETRESOURCEID(PAGE_RECOGNITION_GENERAL, IDD_SETTINGS_RECOGNITION_GENERAL); - SETRESOURCEID(PAGE_RECOGNITION_MEDIA, IDD_SETTINGS_RECOGNITION_MEDIA); - SETRESOURCEID(PAGE_RECOGNITION_STREAM, IDD_SETTINGS_RECOGNITION_STREAM); - SETRESOURCEID(PAGE_SERVICES_MAL, IDD_SETTINGS_SERVICES_MAL); - SETRESOURCEID(PAGE_SHARING_HTTP, IDD_SETTINGS_SHARING_HTTP); - SETRESOURCEID(PAGE_SHARING_MESSENGER, IDD_SETTINGS_SHARING_MESSENGER); - SETRESOURCEID(PAGE_SHARING_MIRC, IDD_SETTINGS_SHARING_MIRC); - SETRESOURCEID(PAGE_SHARING_SKYPE, IDD_SETTINGS_SHARING_SKYPE); - SETRESOURCEID(PAGE_SHARING_TWITTER, IDD_SETTINGS_SHARING_TWITTER); - SETRESOURCEID(PAGE_TORRENTS_DISCOVERY, IDD_SETTINGS_TORRENTS_DISCOVERY); - SETRESOURCEID(PAGE_TORRENTS_DOWNLOADS, IDD_SETTINGS_TORRENTS_DOWNLOADS); - SETRESOURCEID(PAGE_TORRENTS_FILTERS, IDD_SETTINGS_TORRENTS_FILTERS); - #undef SETRESOURCEID - } - - win32::Dialog::Create(resource_id, parent->GetWindowHandle(), false); - SetPosition(nullptr, rect_page, 0); - EnableThemeDialogTexture(GetWindowHandle(), ETDT_ENABLETAB); -} - -// ============================================================================= - -BOOL SettingsPage::OnInitDialog() { - switch (index) { - // Services > MyAnimeList - case PAGE_SERVICES_MAL: { - SetDlgItemText(IDC_EDIT_USER, Settings.Account.MAL.user.c_str()); - SetDlgItemText(IDC_EDIT_PASS, Settings.Account.MAL.password.c_str()); - CheckDlgButton(IDC_CHECK_START_LOGIN, Settings.Account.MAL.auto_sync); - break; - } - - // ========================================================================= - - // Library > Folders - case PAGE_LIBRARY_FOLDERS: { - win32::ListView list = GetDlgItem(IDC_LIST_FOLDERS_ROOT); - list.InsertColumn(0, 0, 0, 0, L"Folder"); - list.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); - list.SetExtendedStyle(LVS_EX_DOUBLEBUFFER); - list.SetImageList(UI.ImgList16.GetHandle()); - list.SetTheme(); - for (size_t i = 0; i < Settings.Folders.root.size(); i++) - list.InsertItem(i, -1, ICON16_FOLDER, 0, nullptr, Settings.Folders.root[i].c_str(), 0); - list.SetWindowHandle(nullptr); - CheckDlgButton(IDC_CHECK_FOLDERS_WATCH, Settings.Folders.watch_enabled); - break; - } - // Library > Cache - case PAGE_LIBRARY_CACHE: { - parent->RefreshCache(); - break; - } - - // ========================================================================= - - // Application > Behavior - case PAGE_APP_BEHAVIOR: { - CheckDlgButton(IDC_CHECK_AUTOSTART, Settings.Program.General.auto_start); - CheckDlgButton(IDC_CHECK_GENERAL_CLOSE, Settings.Program.General.close); - CheckDlgButton(IDC_CHECK_GENERAL_MINIMIZE, Settings.Program.General.minimize); - CheckDlgButton(IDC_CHECK_START_VERSION, Settings.Program.StartUp.check_new_version); - CheckDlgButton(IDC_CHECK_START_CHECKEPS, Settings.Program.StartUp.check_new_episodes); - CheckDlgButton(IDC_CHECK_START_MINIMIZE, Settings.Program.StartUp.minimize); - break; - } - // Application > Connection - case PAGE_APP_CONNECTION: { - SetDlgItemText(IDC_EDIT_PROXY_HOST, Settings.Program.Proxy.host.c_str()); - SetDlgItemText(IDC_EDIT_PROXY_USER, Settings.Program.Proxy.user.c_str()); - SetDlgItemText(IDC_EDIT_PROXY_PASS, Settings.Program.Proxy.password.c_str()); - break; - } - // Application > Interface - case PAGE_APP_INTERFACE: { - vector theme_list; - PopulateFolders(theme_list, Taiga.GetDataPath() + L"Theme\\"); - if (theme_list.empty()) { - EnableDlgItem(IDC_COMBO_THEME, FALSE); - } else { - for (size_t i = 0; i < theme_list.size(); i++) { - AddComboString(IDC_COMBO_THEME, theme_list[i].c_str()); - if (IsEqual(theme_list[i], Settings.Program.General.theme)) - SetComboSelection(IDC_COMBO_THEME, i); - } - } - SetDlgItemText(IDC_EDIT_EXTERNALLINKS, Settings.Program.General.external_links.c_str()); - break; - } - // Application > List - case PAGE_APP_LIST: { - AddComboString(IDC_COMBO_DBLCLICK, L"Do nothing"); - AddComboString(IDC_COMBO_DBLCLICK, L"Edit details"); - AddComboString(IDC_COMBO_DBLCLICK, L"Open folder"); - AddComboString(IDC_COMBO_DBLCLICK, L"Play next episode"); - AddComboString(IDC_COMBO_DBLCLICK, L"View anime info"); - SetComboSelection(IDC_COMBO_DBLCLICK, Settings.Program.List.double_click); - AddComboString(IDC_COMBO_MDLCLICK, L"Do nothing"); - AddComboString(IDC_COMBO_MDLCLICK, L"Edit details"); - AddComboString(IDC_COMBO_MDLCLICK, L"Open folder"); - AddComboString(IDC_COMBO_MDLCLICK, L"Play next episode"); - AddComboString(IDC_COMBO_MDLCLICK, L"View anime info"); - SetComboSelection(IDC_COMBO_MDLCLICK, Settings.Program.List.middle_click); - CheckDlgButton(IDC_CHECK_LIST_ENGLISH, Settings.Program.List.english_titles); - CheckDlgButton(IDC_CHECK_HIGHLIGHT, Settings.Program.List.highlight); - CheckDlgButton(IDC_CHECK_LIST_PROGRESS_AIRED, Settings.Program.List.progress_show_aired); - CheckDlgButton(IDC_CHECK_LIST_PROGRESS_AVAILABLE, Settings.Program.List.progress_show_available); - break; - } - - // ========================================================================= - - // Recognition > General - case PAGE_RECOGNITION_GENERAL: { - CheckDlgButton(IDC_CHECK_UPDATE_CONFIRM, Settings.Account.Update.ask_to_confirm); - CheckDlgButton(IDC_CHECK_UPDATE_CHECKMP, Settings.Account.Update.check_player); - CheckDlgButton(IDC_CHECK_UPDATE_GOTO, Settings.Account.Update.go_to_nowplaying); - CheckDlgButton(IDC_CHECK_UPDATE_RANGE, Settings.Account.Update.out_of_range); - CheckDlgButton(IDC_CHECK_UPDATE_ROOT, Settings.Account.Update.out_of_root); - CheckDlgButton(IDC_CHECK_UPDATE_WAITMP, Settings.Account.Update.wait_mp); - SendDlgItemMessage(IDC_SPIN_DELAY, UDM_SETRANGE32, 0, 3600); - SendDlgItemMessage(IDC_SPIN_DELAY, UDM_SETPOS32, 0, Settings.Account.Update.delay); - CheckDlgButton(IDC_CHECK_NOTIFY_RECOGNIZED, Settings.Program.Notifications.recognized); - CheckDlgButton(IDC_CHECK_NOTIFY_NOTRECOGNIZED, Settings.Program.Notifications.notrecognized); - break; - } - // Recognition > Media players - case PAGE_RECOGNITION_MEDIA: { - win32::ListView list = GetDlgItem(IDC_LIST_MEDIA); - list.EnableGroupView(true); - if (win32::GetWinVersion() >= win32::VERSION_VISTA) { - list.InsertColumn(0, 0, 0, 0, L"Select/deselect all"); - } else { - list.InsertColumn(0, 0, 0, 0, L"Supported players"); - } - list.InsertGroup(0, L"Media players"); - list.InsertGroup(1, L"Web browsers"); - list.SetExtendedStyle(LVS_EX_CHECKBOXES | LVS_EX_DOUBLEBUFFER); - list.SetImageList(UI.ImgList16.GetHandle()); - list.SetTheme(); - if (win32::GetWinVersion() >= win32::VERSION_VISTA) { - win32::Window header = list.GetHeader(); - HDITEM hdi = {0}; - header.SetStyle(HDS_CHECKBOXES, 0); - hdi.mask = HDI_FORMAT; - Header_GetItem(header.GetWindowHandle(), 0, &hdi); - hdi.fmt |= HDF_CHECKBOX; - Header_SetItem(header.GetWindowHandle(), 0, &hdi); - header.SetWindowHandle(nullptr); - } - for (size_t i = 0; i < MediaPlayers.items.size(); i++) { - BOOL player_available = MediaPlayers.items[i].GetPath().empty() ? FALSE : TRUE; - list.InsertItem(i, MediaPlayers.items[i].mode == 5 ? 1 : 0, - ICON16_APP_GRAY - player_available, 0, nullptr, - MediaPlayers.items[i].name.c_str(), 0); - if (MediaPlayers.items[i].enabled) - list.SetCheckState(i, TRUE); - } - list.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); - list.SetWindowHandle(nullptr); - break; - } - // Recognition > Media providers - case PAGE_RECOGNITION_STREAM: { - win32::ListView list = GetDlgItem(IDC_LIST_STREAM_PROVIDER); - list.EnableGroupView(true); - list.InsertColumn(0, 0, 0, 0, L"Media providers"); - list.InsertGroup(0, L"Media providers"); - list.SetExtendedStyle(LVS_EX_CHECKBOXES | LVS_EX_DOUBLEBUFFER); - list.SetImageList(UI.ImgList16.GetHandle()); - list.SetTheme(); - list.InsertItem(0, 0, ICON16_APP_BLUE, 0, nullptr, L"Anime News Network", 0); - list.InsertItem(1, 0, ICON16_APP_BLUE, 0, nullptr, L"Crunchyroll", 1); - list.InsertItem(2, 0, ICON16_APP_BLUE, 0, nullptr, L"Veoh", 3); - list.InsertItem(3, 0, ICON16_APP_BLUE, 0, nullptr, L"Viz Anime", 4); - list.InsertItem(4, 0, ICON16_APP_BLUE, 0, nullptr, L"YouTube", 5); - if (Settings.Recognition.Streaming.ann_enabled) - list.SetCheckState(0, TRUE); - if (Settings.Recognition.Streaming.crunchyroll_enabled) - list.SetCheckState(1, TRUE); - if (Settings.Recognition.Streaming.veoh_enabled) - list.SetCheckState(2, TRUE); - if (Settings.Recognition.Streaming.viz_enabled) - list.SetCheckState(3, TRUE); - if (Settings.Recognition.Streaming.youtube_enabled) - list.SetCheckState(4, TRUE); - list.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); - list.SetWindowHandle(nullptr); - break; - } - - // ========================================================================= - - // Sharing > HTTP - case PAGE_SHARING_HTTP: { - CheckDlgButton(IDC_CHECK_HTTP, Settings.Announce.HTTP.enabled); - SetDlgItemText(IDC_EDIT_HTTP_URL, Settings.Announce.HTTP.url.c_str()); - break; - } - // Sharing > Messenger - case PAGE_SHARING_MESSENGER: { - CheckDlgButton(IDC_CHECK_MESSENGER, Settings.Announce.MSN.enabled); - break; - } - // Sharing > mIRC - case PAGE_SHARING_MIRC: { - CheckDlgButton(IDC_CHECK_MIRC, Settings.Announce.MIRC.enabled); - CheckDlgButton(IDC_CHECK_MIRC_MULTISERVER, Settings.Announce.MIRC.multi_server); - CheckDlgButton(IDC_CHECK_MIRC_ACTION, Settings.Announce.MIRC.use_action); - SetDlgItemText(IDC_EDIT_MIRC_SERVICE, Settings.Announce.MIRC.service.c_str()); - CheckRadioButton(IDC_RADIO_MIRC_CHANNEL1, IDC_RADIO_MIRC_CHANNEL3, - IDC_RADIO_MIRC_CHANNEL1 + Settings.Announce.MIRC.mode - 1); - SetDlgItemText(IDC_EDIT_MIRC_CHANNELS, Settings.Announce.MIRC.channels.c_str()); - EnableDlgItem(IDC_EDIT_MIRC_CHANNELS, Settings.Announce.MIRC.mode == 3); - break; - } - // Sharing > Skype - case PAGE_SHARING_SKYPE: { - CheckDlgButton(IDC_CHECK_SKYPE, Settings.Announce.Skype.enabled); - break; - } - // Sharing > Twitter - case PAGE_SHARING_TWITTER: { - CheckDlgButton(IDC_CHECK_TWITTER, Settings.Announce.Twitter.enabled); - parent->RefreshTwitterLink(); - break; - } - - // ========================================================================= - - // Torrents > Discovery - case PAGE_TORRENTS_DISCOVERY: { - AddComboString(IDC_COMBO_TORRENT_SOURCE, L"http://tokyotosho.info/rss.php?filter=1,11&zwnj=0"); - AddComboString(IDC_COMBO_TORRENT_SOURCE, L"http://www.animesuki.com/rss.php?link=enclosure"); - AddComboString(IDC_COMBO_TORRENT_SOURCE, L"http://www.baka-updates.com/rss.php"); - AddComboString(IDC_COMBO_TORRENT_SOURCE, L"http://www.nyaa.se/?page=rss&cats=1_37&filter=2"); - SetDlgItemText(IDC_COMBO_TORRENT_SOURCE, Settings.RSS.Torrent.source.c_str()); - AddComboString(IDC_COMBO_TORRENT_SEARCH, L"http://www.nyaa.se/?page=rss&cats=1_37&filter=2&term=%title%"); - AddComboString(IDC_COMBO_TORRENT_SEARCH, L"http://pipes.yahoo.com/pipes/pipe.run?SearchQuery=%title%&_id=7b99f981c5b1f02354642f4e271cca43&_render=rss"); - SetDlgItemText(IDC_COMBO_TORRENT_SEARCH, Settings.RSS.Torrent.search_url.c_str()); - CheckDlgButton(IDC_CHECK_TORRENT_AUTOCHECK, Settings.RSS.Torrent.check_enabled); - SendDlgItemMessage(IDC_SPIN_TORRENT_INTERVAL, UDM_SETRANGE32, 0, 3600); - SendDlgItemMessage(IDC_SPIN_TORRENT_INTERVAL, UDM_SETPOS32, 0, Settings.RSS.Torrent.check_interval); - EnableDlgItem(IDC_EDIT_TORRENT_INTERVAL, Settings.RSS.Torrent.check_enabled); - EnableDlgItem(IDC_SPIN_TORRENT_INTERVAL, Settings.RSS.Torrent.check_enabled); - CheckRadioButton(IDC_RADIO_TORRENT_NEW1, IDC_RADIO_TORRENT_NEW2, - IDC_RADIO_TORRENT_NEW1 + Settings.RSS.Torrent.new_action - 1); - break; - } - // Torrents > Downloads - case PAGE_TORRENTS_DOWNLOADS: { - CheckRadioButton(IDC_RADIO_TORRENT_APP1, IDC_RADIO_TORRENT_APP2, - IDC_RADIO_TORRENT_APP1 + Settings.RSS.Torrent.app_mode - 1); - SetDlgItemText(IDC_EDIT_TORRENT_APP, Settings.RSS.Torrent.app_path.c_str()); - EnableDlgItem(IDC_EDIT_TORRENT_APP, Settings.RSS.Torrent.app_mode > 1); - EnableDlgItem(IDC_BUTTON_TORRENT_BROWSE_APP, Settings.RSS.Torrent.app_mode > 1); - CheckDlgButton(IDC_CHECK_TORRENT_AUTOSETFOLDER, Settings.RSS.Torrent.set_folder); - CheckDlgButton(IDC_CHECK_TORRENT_AUTOUSEFOLDER, Settings.RSS.Torrent.use_folder); - CheckDlgButton(IDC_CHECK_TORRENT_AUTOCREATEFOLDER, Settings.RSS.Torrent.create_folder); - for (size_t i = 0; i < Settings.Folders.root.size(); i++) - AddComboString(IDC_COMBO_TORRENT_FOLDER, Settings.Folders.root[i].c_str()); - SetDlgItemText(IDC_COMBO_TORRENT_FOLDER, Settings.RSS.Torrent.download_path.c_str()); - EnableDlgItem(IDC_CHECK_TORRENT_AUTOUSEFOLDER, Settings.RSS.Torrent.set_folder); - EnableDlgItem(IDC_COMBO_TORRENT_FOLDER, Settings.RSS.Torrent.set_folder && Settings.RSS.Torrent.use_folder); - EnableDlgItem(IDC_BUTTON_TORRENT_BROWSE_FOLDER, Settings.RSS.Torrent.set_folder && Settings.RSS.Torrent.use_folder); - EnableDlgItem(IDC_CHECK_TORRENT_AUTOCREATEFOLDER, Settings.RSS.Torrent.set_folder && Settings.RSS.Torrent.use_folder); - break; - } - // Torrents > Filters - case PAGE_TORRENTS_FILTERS: { - CheckDlgButton(IDC_CHECK_TORRENT_FILTER, Settings.RSS.Torrent.Filters.global_enabled); - win32::ListView list = GetDlgItem(IDC_LIST_TORRENT_FILTER); - list.EnableGroupView(true); - list.SetExtendedStyle(LVS_EX_CHECKBOXES | LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP); - list.SetImageList(UI.ImgList16.GetHandle()); - list.SetTheme(); - list.InsertColumn(0, 0, 0, 0, L"Name"); - list.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); - list.InsertGroup(0, L"General filters", true, false); - list.InsertGroup(1, L"Limited filters", true, false); - parent->feed_filters_.resize(Aggregator.filter_manager.filters.size()); - std::copy(Aggregator.filter_manager.filters.begin(), - Aggregator.filter_manager.filters.end(), - parent->feed_filters_.begin()); - parent->RefreshTorrentFilterList(list.GetWindowHandle()); - list.SetWindowHandle(nullptr); - // Initialize toolbar - win32::Toolbar toolbar = GetDlgItem(IDC_TOOLBAR_FEED_FILTER); - toolbar.SetImageList(UI.ImgList16.GetHandle(), 16, 16); - toolbar.SendMessage(TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_MIXEDBUTTONS); - // Add toolbar items - BYTE fsState1 = TBSTATE_ENABLED; - BYTE fsState2 = TBSTATE_INDETERMINATE; - toolbar.InsertButton(0, ICON16_PLUS, 100, fsState1, 0, 0, nullptr, L"Add new filter..."); - toolbar.InsertButton(1, ICON16_MINUS, 101, fsState2, 0, 1, nullptr, L"Delete filter"); - toolbar.InsertButton(2, 0, 0, 0, BTNS_SEP, 0, nullptr, nullptr); - toolbar.InsertButton(3, ICON16_ARROW_UP, 103, fsState2, 0, 3, nullptr, L"Move up"); - toolbar.InsertButton(4, ICON16_ARROW_DOWN, 104, fsState2, 0, 4, nullptr, L"Move down"); - toolbar.SetWindowHandle(nullptr); - break; - } - } - - return TRUE; -} - -// ============================================================================= - -BOOL SettingsPage::OnCommand(WPARAM wParam, LPARAM lParam) { - switch (HIWORD(wParam)) { - case BN_CLICKED: { - switch (LOWORD(wParam)) { - // Edit format - case IDC_BUTTON_FORMAT_HTTP: - FormatDialog.mode = FORMAT_MODE_HTTP; - FormatDialog.Create(IDD_FORMAT, parent->GetWindowHandle(), true); - return TRUE; - case IDC_BUTTON_FORMAT_MSN: - FormatDialog.mode = FORMAT_MODE_MESSENGER; - FormatDialog.Create(IDD_FORMAT, parent->GetWindowHandle(), true); - return TRUE; - case IDC_BUTTON_FORMAT_MIRC: - FormatDialog.mode = FORMAT_MODE_MIRC; - FormatDialog.Create(IDD_FORMAT, parent->GetWindowHandle(), true); - return TRUE; - case IDC_BUTTON_FORMAT_SKYPE: - FormatDialog.mode = FORMAT_MODE_SKYPE; - FormatDialog.Create(IDD_FORMAT, parent->GetWindowHandle(), true); - return TRUE; - case IDC_BUTTON_FORMAT_TWITTER: - FormatDialog.mode = FORMAT_MODE_TWITTER; - FormatDialog.Create(IDD_FORMAT, parent->GetWindowHandle(), true); - return TRUE; - case IDC_BUTTON_FORMAT_BALLOON: - FormatDialog.mode = FORMAT_MODE_BALLOON; - FormatDialog.Create(IDD_FORMAT, parent->GetWindowHandle(), true); - return TRUE; - - // ================================================================================ - - // Add folders - case IDC_BUTTON_ADDFOLDER: { - wstring path; - if (BrowseForFolder(m_hWindow, L"Please select a folder:", L"", path)) { - win32::ListView list = GetDlgItem(IDC_LIST_FOLDERS_ROOT); - list.InsertItem(list.GetItemCount(), -1, ICON16_FOLDER, 0, nullptr, path.c_str(), 0); - list.SetSelectedItem(list.GetItemCount() - 1); - list.SetWindowHandle(nullptr); - } - return TRUE; - } - // Remove folders - case IDC_BUTTON_REMOVEFOLDER: { - win32::ListView list = GetDlgItem(IDC_LIST_FOLDERS_ROOT); - while (list.GetSelectedCount() > 0) - list.DeleteItem(list.GetNextItem(-1, LVNI_SELECTED)); - EnableDlgItem(IDC_BUTTON_REMOVEFOLDER, FALSE); - list.SetWindowHandle(nullptr); - return TRUE; - } - - // ================================================================================ - - // Clear cache - case IDC_BUTTON_CACHE_CLEAR: { - if (IsDlgButtonChecked(IDC_CHECK_CACHE1)) { - History.items.clear(); - History.Save(); - HistoryDialog.RefreshList(); - } - if (IsDlgButtonChecked(IDC_CHECK_CACHE2)) { - wstring path = Taiga.GetDataPath() + L"db\\image"; - DeleteFolder(path); - ImageDatabase.FreeMemory(); - } - if (IsDlgButtonChecked(IDC_CHECK_CACHE3)) { - wstring path = Taiga.GetDataPath() + L"feed"; - DeleteFolder(path); - } - parent->RefreshCache(); - CheckDlgButton(IDC_CHECK_CACHE1, FALSE); - CheckDlgButton(IDC_CHECK_CACHE2, FALSE); - CheckDlgButton(IDC_CHECK_CACHE3, FALSE); - EnableDlgItem(IDC_BUTTON_CACHE_CLEAR, FALSE); - return TRUE; - } - - // ================================================================================ - - // Test DDE connection - case IDC_BUTTON_MIRC_TEST: { - wstring service; - GetDlgItemText(IDC_EDIT_MIRC_SERVICE, service); - Announcer.TestMircConnection(service); - return TRUE; - } - - // ================================================================================ - - // Authorize Twitter - case IDC_BUTTON_TWITTER_AUTH: { - Twitter.RequestToken(); - return TRUE; - } - - // ================================================================================ - - // Browse for torrent application - case IDC_BUTTON_TORRENT_BROWSE_APP: { - wstring current_directory = Taiga.GetCurrentDirectory(); - wstring path = BrowseForFile(m_hWindow, L"Please select a torrent application", - L"Executable files (*.exe)\0*.exe\0\0"); - if (current_directory != Taiga.GetCurrentDirectory()) - Taiga.SetCurrentDirectory(current_directory); - if (!path.empty()) - SetDlgItemText(IDC_EDIT_TORRENT_APP, path.c_str()); - return TRUE; - } - // Browse for torrent download path - case IDC_BUTTON_TORRENT_BROWSE_FOLDER: { - wstring path; - if (BrowseForFolder(m_hWindow, L"Please select a folder:", L"", path)) - SetDlgItemText(IDC_COMBO_TORRENT_FOLDER, path.c_str()); - return TRUE; - } - // Enable/disable controls - case IDC_CHECK_CACHE1: - case IDC_CHECK_CACHE2: - case IDC_CHECK_CACHE3: { - bool enable = IsDlgButtonChecked(IDC_CHECK_CACHE1) || - IsDlgButtonChecked(IDC_CHECK_CACHE2) || - IsDlgButtonChecked(IDC_CHECK_CACHE3); - EnableDlgItem(IDC_BUTTON_CACHE_CLEAR, enable); - return TRUE; - } - case IDC_CHECK_TORRENT_AUTOCHECK: { - BOOL enable = IsDlgButtonChecked(LOWORD(wParam)); - EnableDlgItem(IDC_EDIT_TORRENT_INTERVAL, enable); - EnableDlgItem(IDC_SPIN_TORRENT_INTERVAL, enable); - return TRUE; - } - case IDC_CHECK_TORRENT_AUTOSETFOLDER: { - BOOL enable = IsDlgButtonChecked(LOWORD(wParam)); - EnableDlgItem(IDC_CHECK_TORRENT_AUTOUSEFOLDER, enable); - enable = enable && IsDlgButtonChecked(IDC_CHECK_TORRENT_AUTOUSEFOLDER); - EnableDlgItem(IDC_COMBO_TORRENT_FOLDER, enable); - EnableDlgItem(IDC_BUTTON_TORRENT_BROWSE_FOLDER, enable); - EnableDlgItem(IDC_CHECK_TORRENT_AUTOCREATEFOLDER, enable); - return TRUE; - } - case IDC_CHECK_TORRENT_AUTOUSEFOLDER: { - BOOL enable = IsDlgButtonChecked(LOWORD(wParam)); - EnableDlgItem(IDC_COMBO_TORRENT_FOLDER, enable); - EnableDlgItem(IDC_BUTTON_TORRENT_BROWSE_FOLDER, enable); - EnableDlgItem(IDC_CHECK_TORRENT_AUTOCREATEFOLDER, enable); - return TRUE; - } - // Enable/disable filters - case IDC_CHECK_TORRENT_FILTER: { - BOOL enable = IsDlgButtonChecked(LOWORD(wParam)); - EnableDlgItem(IDC_LIST_TORRENT_FILTER, enable); - EnableDlgItem(IDC_TOOLBAR_FEED_FILTER, enable); - return TRUE; - } - // Add global filter - case 100: { - FeedFilterDialog.filter.Reset(); - ExecuteAction(L"TorrentAddFilter", TRUE, reinterpret_cast(parent->GetWindowHandle())); - if (!FeedFilterDialog.filter.conditions.empty()) { - if (FeedFilterDialog.filter.name.empty()) - FeedFilterDialog.filter.name = Aggregator.filter_manager.CreateNameFromConditions(FeedFilterDialog.filter); - parent->feed_filters_.push_back(FeedFilterDialog.filter); - win32::ListView list = GetDlgItem(IDC_LIST_TORRENT_FILTER); - parent->RefreshTorrentFilterList(list.GetWindowHandle()); - list.SetSelectedItem(list.GetItemCount() - 1); - list.SetWindowHandle(nullptr); - } - return TRUE; - } - // Remove global filter - case 101: { - win32::ListView list = GetDlgItem(IDC_LIST_TORRENT_FILTER); - int item_index = list.GetNextItem(-1, LVNI_SELECTED); - FeedFilter* feed_filter = reinterpret_cast(list.GetItemParam(item_index)); - for (auto it = parent->feed_filters_.begin(); it != parent->feed_filters_.end(); ++it) { - if (feed_filter == &(*it)) { - parent->feed_filters_.erase(it); - parent->RefreshTorrentFilterList(list.GetWindowHandle()); - break; - } - } - list.SetWindowHandle(nullptr); - return TRUE; - } - // Move filter up - case 103: { - win32::ListView list = GetDlgItem(IDC_LIST_TORRENT_FILTER); - int index = list.GetNextItem(-1, LVNI_SELECTED); - if (index > 0) { - iter_swap(parent->feed_filters_.begin() + index, - parent->feed_filters_.begin() + index - 1); - parent->RefreshTorrentFilterList(list.GetWindowHandle()); - list.SetSelectedItem(index - 1); - } - list.SetWindowHandle(nullptr); - return TRUE; - } - // Move filter down - case 104: { - win32::ListView list = GetDlgItem(IDC_LIST_TORRENT_FILTER); - int index = list.GetNextItem(-1, LVNI_SELECTED); - if (index > -1 && index < list.GetItemCount() - 1) { - iter_swap(parent->feed_filters_.begin() + index, - parent->feed_filters_.begin() + index + 1); - parent->RefreshTorrentFilterList(list.GetWindowHandle()); - list.SetSelectedItem(index + 1); - } - list.SetWindowHandle(nullptr); - return TRUE; - } - - // ================================================================================ - - // Check radio buttons - case IDC_RADIO_MIRC_CHANNEL1: - case IDC_RADIO_MIRC_CHANNEL2: - case IDC_RADIO_MIRC_CHANNEL3: - CheckRadioButton(IDC_RADIO_MIRC_CHANNEL1, IDC_RADIO_MIRC_CHANNEL3, LOWORD(wParam)); - EnableDlgItem(IDC_EDIT_MIRC_CHANNELS, LOWORD(wParam) == IDC_RADIO_MIRC_CHANNEL3); - return TRUE; - case IDC_RADIO_TORRENT_NEW1: - case IDC_RADIO_TORRENT_NEW2: - CheckRadioButton(IDC_RADIO_TORRENT_NEW1, IDC_RADIO_TORRENT_NEW2, LOWORD(wParam)); - return TRUE; - case IDC_RADIO_TORRENT_APP1: - case IDC_RADIO_TORRENT_APP2: - CheckRadioButton(IDC_RADIO_TORRENT_APP1, IDC_RADIO_TORRENT_APP2, LOWORD(wParam)); - EnableDlgItem(IDC_EDIT_TORRENT_APP, LOWORD(wParam) == IDC_RADIO_TORRENT_APP2); - EnableDlgItem(IDC_BUTTON_TORRENT_BROWSE_APP, LOWORD(wParam) == IDC_RADIO_TORRENT_APP2); - return TRUE; - } - break; - } - } - - return FALSE; -} - -// ============================================================================= - -void SettingsPage::OnDropFiles(HDROP hDropInfo) { - if (index != PAGE_LIBRARY_FOLDERS) - return; - - WCHAR szFileName[MAX_PATH + 1]; - UINT nFiles = DragQueryFile(hDropInfo, static_cast(-1), nullptr, 0); - win32::ListView list = GetDlgItem(IDC_LIST_FOLDERS_ROOT); - for (UINT i = 0; i < nFiles; i++) { - ZeroMemory(szFileName, MAX_PATH + 1); - DragQueryFile(hDropInfo, i, (LPWSTR)szFileName, MAX_PATH + 1); - if (GetFileAttributes(szFileName) & FILE_ATTRIBUTE_DIRECTORY) { - list.InsertItem(list.GetItemCount(), -1, ICON16_FOLDER, 0, nullptr, szFileName, 0); - list.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); - } - } - list.SetWindowHandle(nullptr); -} - -LRESULT SettingsPage::OnNotify(int idCtrl, LPNMHDR pnmh) { - switch (pnmh->code) { - // Header checkbox click - case HDN_ITEMCHANGED: { - win32::ListView list = GetDlgItem(IDC_LIST_MEDIA); - if (pnmh->hwndFrom == list.GetHeader()) { - auto nmh = reinterpret_cast(pnmh); - if (nmh->pitem->mask & HDI_FORMAT) { - BOOL checked = nmh->pitem->fmt & HDF_CHECKED ? TRUE : FALSE; - for (size_t i = 0; i < MediaPlayers.items.size(); i++) - list.SetCheckState(i, checked); - } - } - list.SetWindowHandle(nullptr); - break; - } - - case NM_CLICK: { - switch (pnmh->idFrom) { - // Execute link - case IDC_LINK_MAL: - case IDC_LINK_TWITTER: { - PNMLINK pNMLink = reinterpret_cast(pnmh); - ExecuteAction(pNMLink->item.szUrl); - return TRUE; - } - // Open themes folder - case IDC_LINK_THEMES: { - wstring theme_name; - GetDlgItemText(IDC_COMBO_THEME, theme_name); - wstring path = Taiga.GetDataPath() + L"theme\\" + theme_name; - Execute(path); - return TRUE; - } - } - } - - // List item select - case LVN_ITEMCHANGED: { - LPNMLISTVIEW lplv = reinterpret_cast(pnmh); - if (lplv->hdr.hwndFrom == GetDlgItem(IDC_LIST_FOLDERS_ROOT)) { - EnableDlgItem(IDC_BUTTON_REMOVEFOLDER, ListView_GetSelectedCount(lplv->hdr.hwndFrom) > 0); - } else if (lplv->hdr.hwndFrom == GetDlgItem(IDC_LIST_TORRENT_FILTER)) { - win32::ListView list = GetDlgItem(IDC_LIST_TORRENT_FILTER); - win32::Toolbar toolbar = GetDlgItem(IDC_TOOLBAR_FEED_FILTER); - int index = list.GetNextItem(-1, LVNI_SELECTED); - int count = list.GetItemCount(); - toolbar.EnableButton(101, index > -1); - toolbar.EnableButton(103, index > 0); - toolbar.EnableButton(104, index > -1 && index < count - 1); - list.SetWindowHandle(nullptr); - toolbar.SetWindowHandle(nullptr); - } - break; - } - - // Text callback - case LVN_GETDISPINFO: { - NMLVDISPINFO* plvdi = reinterpret_cast(pnmh); - auto anime_item = AnimeDatabase.FindItem(static_cast(plvdi->item.lParam)); - if (!anime_item) break; - switch (plvdi->item.iSubItem) { - case 0: // Anime title - plvdi->item.pszText = const_cast(anime_item->GetTitle().data()); - break; - } - break; - } - case TBN_GETINFOTIP: { - if (pnmh->hwndFrom == GetDlgItem(IDC_TOOLBAR_FEED_FILTER)) { - win32::Toolbar toolbar = GetDlgItem(IDC_TOOLBAR_FEED_FILTER); - NMTBGETINFOTIP* git = reinterpret_cast(pnmh); - git->cchTextMax = INFOTIPSIZE; - git->pszText = (LPWSTR)(toolbar.GetButtonTooltip(git->lParam)); - toolbar.SetWindowHandle(nullptr); - } - break; - } - - // Double click - case NM_DBLCLK: { - LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); - if (lpnmitem->iItem == -1) break; - // Anime folders - if (lpnmitem->hdr.hwndFrom == GetDlgItem(IDC_LIST_FOLDERS_ROOT)) { - win32::ListView list = lpnmitem->hdr.hwndFrom; - WCHAR buffer[MAX_PATH]; - list.GetItemText(lpnmitem->iItem, 0, buffer); - Execute(buffer); - list.SetWindowHandle(nullptr); - // Media players - } else if (lpnmitem->hdr.hwndFrom == GetDlgItem(IDC_LIST_MEDIA)) { - Execute(MediaPlayers.items[lpnmitem->iItem].GetPath()); - // Streaming media providers - } else if (lpnmitem->hdr.hwndFrom == GetDlgItem(IDC_LIST_STREAM_PROVIDER)) { - switch (lpnmitem->iItem) { - case 0: - ExecuteLink(L"http://www.animenewsnetwork.com/video/"); - break; - case 1: - ExecuteLink(L"http://www.crunchyroll.com"); - break; - case 2: - ExecuteLink(L"http://www.veoh.com"); - break; - case 3: - ExecuteLink(L"http://www.vizanime.com"); - break; - case 4: - ExecuteLink(L"http://www.youtube.com"); - break; - } - // Torrent filters - } else if (lpnmitem->hdr.hwndFrom == GetDlgItem(IDC_LIST_TORRENT_FILTER)) { - win32::ListView list = lpnmitem->hdr.hwndFrom; - FeedFilter* feed_filter = reinterpret_cast(list.GetItemParam(lpnmitem->iItem)); - if (feed_filter) { - FeedFilterDialog.filter = *feed_filter; - FeedFilterDialog.Create(IDD_FEED_FILTER, parent->GetWindowHandle()); - if (!FeedFilterDialog.filter.conditions.empty()) { - *feed_filter = FeedFilterDialog.filter; - parent->RefreshTorrentFilterList(lpnmitem->hdr.hwndFrom); - list.SetSelectedItem(lpnmitem->iItem); - } - } - list.SetWindowHandle(nullptr); - } - return TRUE; - } - - // Right click - case NM_RCLICK: { - LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); - if (lpnmitem->iItem == -1) break; - win32::ListView list = lpnmitem->hdr.hwndFrom; - // Media players - if (lpnmitem->hdr.hwndFrom == GetDlgItem(IDC_LIST_MEDIA)) { - wstring answer = UI.Menus.Show(GetWindowHandle(), 0, 0, L"GenericList"); - for (int i = 0; i < list.GetItemCount(); i++) { - if (answer == L"SelectAll()") { - list.SetCheckState(i, TRUE); - } else if (answer == L"DeselectAll()") { - list.SetCheckState(i, FALSE); - } - } - } - list.SetWindowHandle(nullptr); - return TRUE; - } - } - - return 0; -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/crypto.h" +#include "base/file.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/history.h" +#include "library/resource.h" +#include "sync/manager.h" +#include "taiga/announce.h" +#include "taiga/path.h" +#include "taiga/resource.h" +#include "taiga/script.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" +#include "track/media.h" +#include "ui/dlg/dlg_feed_filter.h" +#include "ui/dlg/dlg_format.h" +#include "ui/dlg/dlg_settings.h" +#include "ui/dlg/dlg_settings_page.h" +#include "ui/menu.h" +#include "ui/theme.h" +#include "win/win_commondialog.h" + +namespace ui { + +SettingsPage::SettingsPage() + : index(-1), parent(nullptr) { +} + +void SettingsPage::Create() { + win::Rect rect_page; + parent->tab_.GetWindowRect(parent->GetWindowHandle(), &rect_page); + parent->tab_.AdjustRect(nullptr, FALSE, &rect_page); + + UINT resource_id = 0; + switch (index) { + #define SETRESOURCEID(page, id) case page: resource_id = id; break; + SETRESOURCEID(kSettingsPageAppBehavior, IDD_SETTINGS_APP_BEHAVIOR); + SETRESOURCEID(kSettingsPageAppConnection, IDD_SETTINGS_APP_CONNECTION); + SETRESOURCEID(kSettingsPageAppInterface, IDD_SETTINGS_APP_INTERFACE); + SETRESOURCEID(kSettingsPageAppList, IDD_SETTINGS_APP_LIST); + SETRESOURCEID(kSettingsPageLibraryFolders, IDD_SETTINGS_LIBRARY_FOLDERS); + SETRESOURCEID(kSettingsPageLibraryCache, IDD_SETTINGS_LIBRARY_CACHE); + SETRESOURCEID(kSettingsPageRecognitionGeneral, IDD_SETTINGS_RECOGNITION_GENERAL); + SETRESOURCEID(kSettingsPageRecognitionMedia, IDD_SETTINGS_RECOGNITION_MEDIA); + SETRESOURCEID(kSettingsPageRecognitionStream, IDD_SETTINGS_RECOGNITION_STREAM); + SETRESOURCEID(kSettingsPageServicesMain, IDD_SETTINGS_SERVICES_MAIN); + SETRESOURCEID(kSettingsPageServicesMal, IDD_SETTINGS_SERVICES_MAL); + SETRESOURCEID(kSettingsPageServicesHummingbird, IDD_SETTINGS_SERVICES_HUMMINGBIRD); + SETRESOURCEID(kSettingsPageSharingHttp, IDD_SETTINGS_SHARING_HTTP); + SETRESOURCEID(kSettingsPageSharingMirc, IDD_SETTINGS_SHARING_MIRC); + SETRESOURCEID(kSettingsPageSharingSkype, IDD_SETTINGS_SHARING_SKYPE); + SETRESOURCEID(kSettingsPageSharingTwitter, IDD_SETTINGS_SHARING_TWITTER); + SETRESOURCEID(kSettingsPageTorrentsDiscovery, IDD_SETTINGS_TORRENTS_DISCOVERY); + SETRESOURCEID(kSettingsPageTorrentsDownloads, IDD_SETTINGS_TORRENTS_DOWNLOADS); + SETRESOURCEID(kSettingsPageTorrentsFilters, IDD_SETTINGS_TORRENTS_FILTERS); + #undef SETRESOURCEID + } + + win::Dialog::Create(resource_id, parent->GetWindowHandle(), false); + SetPosition(nullptr, rect_page, 0); + EnableThemeDialogTexture(GetWindowHandle(), ETDT_ENABLETAB); +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL SettingsPage::OnInitDialog() { + switch (index) { + // Services > Main + case kSettingsPageServicesMain: { + win::ComboBox combo(GetDlgItem(IDC_COMBO_SERVICE)); + for (int i = sync::kFirstService; i <= sync::kLastService; i++) { + auto service = ServiceManager.service(static_cast(i)); + combo.AddItem(service->name().c_str(), service->id()); + auto active_service = ServiceManager.service(Settings[taiga::kSync_ActiveService]); + if (service->id() == active_service->id()) + combo.SetCurSel(combo.GetCount() - 1); + } + combo.SetWindowHandle(nullptr); + CheckDlgButton(IDC_CHECK_START_LOGIN, Settings.GetBool(taiga::kSync_AutoOnStart)); + break; + } + // Services > MyAnimeList + case kSettingsPageServicesMal: { + SetDlgItemText(IDC_EDIT_USER_MAL, Settings[taiga::kSync_Service_Mal_Username].c_str()); + SetDlgItemText(IDC_EDIT_PASS_MAL, SimpleDecrypt(Settings[taiga::kSync_Service_Mal_Password]).c_str()); + break; + } + // Services > Hummingbird + case kSettingsPageServicesHummingbird: { + SetDlgItemText(IDC_EDIT_USER_HUMMINGBIRD, Settings[taiga::kSync_Service_Hummingbird_Username].c_str()); + SetDlgItemText(IDC_EDIT_PASS_HUMMINGBIRD, SimpleDecrypt(Settings[taiga::kSync_Service_Hummingbird_Password]).c_str()); + break; + } + + //////////////////////////////////////////////////////////////////////////// + + // Library > Folders + case kSettingsPageLibraryFolders: { + win::ListView list = GetDlgItem(IDC_LIST_FOLDERS_ROOT); + list.InsertColumn(0, 0, 0, 0, L"Folder"); + list.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); + list.SetExtendedStyle(LVS_EX_DOUBLEBUFFER); + list.SetImageList(ui::Theme.GetImageList16().GetHandle()); + list.SetTheme(); + for (size_t i = 0; i < Settings.root_folders.size(); i++) + list.InsertItem(i, -1, ui::kIcon16_Folder, 0, nullptr, Settings.root_folders[i].c_str(), 0); + list.SetWindowHandle(nullptr); + CheckDlgButton(IDC_CHECK_FOLDERS_WATCH, Settings.GetBool(taiga::kLibrary_WatchFolders)); + break; + } + // Library > Cache + case kSettingsPageLibraryCache: { + parent->RefreshCache(); + break; + } + + //////////////////////////////////////////////////////////////////////////// + + // Application > Behavior + case kSettingsPageAppBehavior: { + CheckDlgButton(IDC_CHECK_AUTOSTART, Settings.GetBool(taiga::kApp_Behavior_Autostart)); + CheckDlgButton(IDC_CHECK_GENERAL_CLOSE, Settings.GetBool(taiga::kApp_Behavior_CloseToTray)); + CheckDlgButton(IDC_CHECK_GENERAL_MINIMIZE, Settings.GetBool(taiga::kApp_Behavior_MinimizeToTray)); + CheckDlgButton(IDC_CHECK_START_VERSION, Settings.GetBool(taiga::kApp_Behavior_CheckForUpdates)); + CheckDlgButton(IDC_CHECK_START_CHECKEPS, Settings.GetBool(taiga::kApp_Behavior_ScanAvailableEpisodes)); + CheckDlgButton(IDC_CHECK_START_MINIMIZE, Settings.GetBool(taiga::kApp_Behavior_StartMinimized)); + break; + } + // Application > Connection + case kSettingsPageAppConnection: { + SetDlgItemText(IDC_EDIT_PROXY_HOST, Settings[taiga::kApp_Connection_ProxyHost].c_str()); + SetDlgItemText(IDC_EDIT_PROXY_USER, Settings[taiga::kApp_Connection_ProxyUsername].c_str()); + SetDlgItemText(IDC_EDIT_PROXY_PASS, Settings[taiga::kApp_Connection_ProxyPassword].c_str()); + break; + } + // Application > Interface + case kSettingsPageAppInterface: { + std::vector theme_list; + PopulateFolders(theme_list, taiga::GetPath(taiga::kPathTheme)); + if (theme_list.empty()) { + EnableDlgItem(IDC_COMBO_THEME, FALSE); + } else { + for (size_t i = 0; i < theme_list.size(); i++) { + AddComboString(IDC_COMBO_THEME, theme_list[i].c_str()); + if (IsEqual(theme_list[i], Settings[taiga::kApp_Interface_Theme])) + SetComboSelection(IDC_COMBO_THEME, i); + } + } + SetDlgItemText(IDC_EDIT_EXTERNALLINKS, Settings[taiga::kApp_Interface_ExternalLinks].c_str()); + break; + } + // Application > List + case kSettingsPageAppList: { + AddComboString(IDC_COMBO_DBLCLICK, L"Do nothing"); + AddComboString(IDC_COMBO_DBLCLICK, L"Edit details"); + AddComboString(IDC_COMBO_DBLCLICK, L"Open folder"); + AddComboString(IDC_COMBO_DBLCLICK, L"Play next episode"); + AddComboString(IDC_COMBO_DBLCLICK, L"View anime info"); + SetComboSelection(IDC_COMBO_DBLCLICK, Settings.GetInt(taiga::kApp_List_DoubleClickAction)); + AddComboString(IDC_COMBO_MDLCLICK, L"Do nothing"); + AddComboString(IDC_COMBO_MDLCLICK, L"Edit details"); + AddComboString(IDC_COMBO_MDLCLICK, L"Open folder"); + AddComboString(IDC_COMBO_MDLCLICK, L"Play next episode"); + AddComboString(IDC_COMBO_MDLCLICK, L"View anime info"); + SetComboSelection(IDC_COMBO_MDLCLICK, Settings.GetInt(taiga::kApp_List_MiddleClickAction)); + CheckDlgButton(IDC_CHECK_LIST_ENGLISH, Settings.GetBool(taiga::kApp_List_DisplayEnglishTitles)); + CheckDlgButton(IDC_CHECK_HIGHLIGHT, Settings.GetBool(taiga::kApp_List_HighlightNewEpisodes)); + CheckDlgButton(IDC_CHECK_LIST_PROGRESS_AIRED, Settings.GetBool(taiga::kApp_List_ProgressDisplayAired)); + CheckDlgButton(IDC_CHECK_LIST_PROGRESS_AVAILABLE, Settings.GetBool(taiga::kApp_List_ProgressDisplayAvailable)); + break; + } + + //////////////////////////////////////////////////////////////////////////// + + // Recognition > General + case kSettingsPageRecognitionGeneral: { + CheckDlgButton(IDC_CHECK_UPDATE_CONFIRM, Settings.GetBool(taiga::kSync_Update_AskToConfirm)); + CheckDlgButton(IDC_CHECK_UPDATE_CHECKMP, Settings.GetBool(taiga::kSync_Update_CheckPlayer)); + CheckDlgButton(IDC_CHECK_UPDATE_GOTO, Settings.GetBool(taiga::kSync_Update_GoToNowPlaying)); + CheckDlgButton(IDC_CHECK_UPDATE_RANGE, Settings.GetBool(taiga::kSync_Update_OutOfRange)); + CheckDlgButton(IDC_CHECK_UPDATE_ROOT, Settings.GetBool(taiga::kSync_Update_OutOfRoot)); + CheckDlgButton(IDC_CHECK_UPDATE_WAITMP, Settings.GetBool(taiga::kSync_Update_WaitPlayer)); + SendDlgItemMessage(IDC_SPIN_DELAY, UDM_SETRANGE32, 10, 3600); + SendDlgItemMessage(IDC_SPIN_DELAY, UDM_SETPOS32, 0, Settings.GetInt(taiga::kSync_Update_Delay)); + CheckDlgButton(IDC_CHECK_NOTIFY_RECOGNIZED, Settings.GetBool(taiga::kSync_Notify_Recognized)); + CheckDlgButton(IDC_CHECK_NOTIFY_NOTRECOGNIZED, Settings.GetBool(taiga::kSync_Notify_NotRecognized)); + break; + } + // Recognition > Media players + case kSettingsPageRecognitionMedia: { + win::ListView list = GetDlgItem(IDC_LIST_MEDIA); + list.EnableGroupView(true); + if (win::GetVersion() >= win::kVersionVista) { + list.InsertColumn(0, 0, 0, 0, L"Select/deselect all"); + } else { + list.InsertColumn(0, 0, 0, 0, L"Supported players"); + } + list.InsertGroup(0, L"Media players"); + list.InsertGroup(1, L"Web browsers"); + list.SetExtendedStyle(LVS_EX_CHECKBOXES | LVS_EX_DOUBLEBUFFER); + list.SetImageList(ui::Theme.GetImageList16().GetHandle()); + list.SetTheme(); + if (win::GetVersion() >= win::kVersionVista) { + win::Window header = list.GetHeader(); + HDITEM hdi = {0}; + header.SetStyle(HDS_CHECKBOXES, 0); + hdi.mask = HDI_FORMAT; + Header_GetItem(header.GetWindowHandle(), 0, &hdi); + hdi.fmt |= HDF_CHECKBOX; + Header_SetItem(header.GetWindowHandle(), 0, &hdi); + header.SetWindowHandle(nullptr); + } + for (size_t i = 0; i < MediaPlayers.items.size(); i++) { + BOOL player_available = MediaPlayers.items[i].GetPath().empty() ? FALSE : TRUE; + list.InsertItem(i, MediaPlayers.items[i].mode == 5 ? 1 : 0, + ui::kIcon16_AppGray - player_available, 0, nullptr, + MediaPlayers.items[i].name.c_str(), 0); + if (MediaPlayers.items[i].enabled) + list.SetCheckState(i, TRUE); + } + list.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); + list.SetWindowHandle(nullptr); + break; + } + // Recognition > Media providers + case kSettingsPageRecognitionStream: { + win::ListView list = GetDlgItem(IDC_LIST_STREAM_PROVIDER); + list.EnableGroupView(true); + list.InsertColumn(0, 0, 0, 0, L"Media providers"); + list.InsertGroup(0, L"Media providers"); + list.SetExtendedStyle(LVS_EX_CHECKBOXES | LVS_EX_DOUBLEBUFFER); + list.SetImageList(ui::Theme.GetImageList16().GetHandle()); + list.SetTheme(); + list.InsertItem(0, 0, ui::kIcon16_AppBlue, 0, nullptr, L"Anime News Network", 0); + list.InsertItem(1, 0, ui::kIcon16_AppBlue, 0, nullptr, L"Crunchyroll", 1); + list.InsertItem(2, 0, ui::kIcon16_AppBlue, 0, nullptr, L"Veoh", 3); + list.InsertItem(3, 0, ui::kIcon16_AppBlue, 0, nullptr, L"Viz Anime", 4); + list.InsertItem(4, 0, ui::kIcon16_AppBlue, 0, nullptr, L"YouTube", 5); + if (Settings.GetBool(taiga::kStream_Ann)) + list.SetCheckState(0, TRUE); + if (Settings.GetBool(taiga::kStream_Crunchyroll)) + list.SetCheckState(1, TRUE); + if (Settings.GetBool(taiga::kStream_Veoh)) + list.SetCheckState(2, TRUE); + if (Settings.GetBool(taiga::kStream_Viz)) + list.SetCheckState(3, TRUE); + if (Settings.GetBool(taiga::kStream_Youtube)) + list.SetCheckState(4, TRUE); + list.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); + list.SetWindowHandle(nullptr); + break; + } + + //////////////////////////////////////////////////////////////////////////// + + // Sharing > HTTP + case kSettingsPageSharingHttp: { + CheckDlgButton(IDC_CHECK_HTTP, Settings.GetBool(taiga::kShare_Http_Enabled)); + SetDlgItemText(IDC_EDIT_HTTP_URL, Settings[taiga::kShare_Http_Url].c_str()); + break; + } + // Sharing > mIRC + case kSettingsPageSharingMirc: { + CheckDlgButton(IDC_CHECK_MIRC, Settings.GetBool(taiga::kShare_Mirc_Enabled)); + CheckDlgButton(IDC_CHECK_MIRC_MULTISERVER, Settings.GetBool(taiga::kShare_Mirc_MultiServer)); + CheckDlgButton(IDC_CHECK_MIRC_ACTION, Settings.GetBool(taiga::kShare_Mirc_UseMeAction)); + SetDlgItemText(IDC_EDIT_MIRC_SERVICE, Settings[taiga::kShare_Mirc_Service].c_str()); + CheckRadioButton(IDC_RADIO_MIRC_CHANNEL1, IDC_RADIO_MIRC_CHANNEL3, + IDC_RADIO_MIRC_CHANNEL1 + Settings.GetInt(taiga::kShare_Mirc_Mode) - 1); + SetDlgItemText(IDC_EDIT_MIRC_CHANNELS, Settings[taiga::kShare_Mirc_Channels].c_str()); + EnableDlgItem(IDC_EDIT_MIRC_CHANNELS, Settings.GetInt(taiga::kShare_Mirc_Mode) == 3); + break; + } + // Sharing > Skype + case kSettingsPageSharingSkype: { + CheckDlgButton(IDC_CHECK_SKYPE, Settings.GetBool(taiga::kShare_Skype_Enabled)); + break; + } + // Sharing > Twitter + case kSettingsPageSharingTwitter: { + CheckDlgButton(IDC_CHECK_TWITTER, Settings.GetBool(taiga::kShare_Twitter_Enabled)); + parent->RefreshTwitterLink(); + break; + } + + //////////////////////////////////////////////////////////////////////////// + + // Torrents > Discovery + case kSettingsPageTorrentsDiscovery: { + AddComboString(IDC_COMBO_TORRENT_SOURCE, L"http://tokyotosho.info/rss.php?filter=1,11&zwnj=0"); + AddComboString(IDC_COMBO_TORRENT_SOURCE, L"http://www.animesuki.com/rss.php?link=enclosure"); + AddComboString(IDC_COMBO_TORRENT_SOURCE, L"http://www.baka-updates.com/rss.php"); + AddComboString(IDC_COMBO_TORRENT_SOURCE, L"http://www.nyaa.se/?page=rss&cats=1_37&filter=2"); + SetDlgItemText(IDC_COMBO_TORRENT_SOURCE, Settings[taiga::kTorrent_Discovery_Source].c_str()); + AddComboString(IDC_COMBO_TORRENT_SEARCH, L"http://www.nyaa.se/?page=rss&cats=1_37&filter=2&term=%title%"); + AddComboString(IDC_COMBO_TORRENT_SEARCH, L"http://pipes.yahoo.com/pipes/pipe.run?SearchQuery=%title%&_id=7b99f981c5b1f02354642f4e271cca43&_render=rss"); + SetDlgItemText(IDC_COMBO_TORRENT_SEARCH, Settings[taiga::kTorrent_Discovery_SearchUrl].c_str()); + CheckDlgButton(IDC_CHECK_TORRENT_AUTOCHECK, Settings.GetBool(taiga::kTorrent_Discovery_AutoCheckEnabled)); + SendDlgItemMessage(IDC_SPIN_TORRENT_INTERVAL, UDM_SETRANGE32, 10, 3600); + SendDlgItemMessage(IDC_SPIN_TORRENT_INTERVAL, UDM_SETPOS32, 0, Settings.GetInt(taiga::kTorrent_Discovery_AutoCheckInterval)); + EnableDlgItem(IDC_EDIT_TORRENT_INTERVAL, Settings.GetBool(taiga::kTorrent_Discovery_AutoCheckEnabled)); + EnableDlgItem(IDC_SPIN_TORRENT_INTERVAL, Settings.GetBool(taiga::kTorrent_Discovery_AutoCheckEnabled)); + CheckRadioButton(IDC_RADIO_TORRENT_NEW1, IDC_RADIO_TORRENT_NEW2, + IDC_RADIO_TORRENT_NEW1 + Settings.GetInt(taiga::kTorrent_Discovery_NewAction) - 1); + EnableDlgItem(IDC_RADIO_TORRENT_NEW1, Settings.GetBool(taiga::kTorrent_Discovery_AutoCheckEnabled)); + EnableDlgItem(IDC_RADIO_TORRENT_NEW2, Settings.GetBool(taiga::kTorrent_Discovery_AutoCheckEnabled)); + break; + } + // Torrents > Downloads + case kSettingsPageTorrentsDownloads: { + CheckRadioButton(IDC_RADIO_TORRENT_APP1, IDC_RADIO_TORRENT_APP2, + IDC_RADIO_TORRENT_APP1 + Settings.GetInt(taiga::kTorrent_Download_AppMode) - 1); + SetDlgItemText(IDC_EDIT_TORRENT_APP, Settings[taiga::kTorrent_Download_AppPath].c_str()); + EnableDlgItem(IDC_EDIT_TORRENT_APP, Settings.GetInt(taiga::kTorrent_Download_AppMode) > 1); + EnableDlgItem(IDC_BUTTON_TORRENT_BROWSE_APP, Settings.GetInt(taiga::kTorrent_Download_AppMode) > 1); + CheckDlgButton(IDC_CHECK_TORRENT_AUTOSETFOLDER, Settings.GetBool(taiga::kTorrent_Download_UseAnimeFolder)); + CheckDlgButton(IDC_CHECK_TORRENT_AUTOUSEFOLDER, Settings.GetBool(taiga::kTorrent_Download_FallbackOnFolder)); + CheckDlgButton(IDC_CHECK_TORRENT_AUTOCREATEFOLDER, Settings.GetBool(taiga::kTorrent_Download_CreateSubfolder)); + for (size_t i = 0; i < Settings.root_folders.size(); i++) + AddComboString(IDC_COMBO_TORRENT_FOLDER, Settings.root_folders[i].c_str()); + SetDlgItemText(IDC_COMBO_TORRENT_FOLDER, Settings[taiga::kTorrent_Download_Location].c_str()); + EnableDlgItem(IDC_CHECK_TORRENT_AUTOUSEFOLDER, Settings.GetBool(taiga::kTorrent_Download_UseAnimeFolder)); + bool enabled = Settings.GetBool(taiga::kTorrent_Download_UseAnimeFolder) && + Settings.GetBool(taiga::kTorrent_Download_FallbackOnFolder); + EnableDlgItem(IDC_COMBO_TORRENT_FOLDER, enabled); + EnableDlgItem(IDC_BUTTON_TORRENT_BROWSE_FOLDER, enabled); + EnableDlgItem(IDC_CHECK_TORRENT_AUTOCREATEFOLDER, enabled); + break; + } + // Torrents > Filters + case kSettingsPageTorrentsFilters: { + CheckDlgButton(IDC_CHECK_TORRENT_FILTER, Settings.GetBool(taiga::kTorrent_Filter_Enabled)); + win::ListView list = GetDlgItem(IDC_LIST_TORRENT_FILTER); + list.EnableGroupView(true); + list.SetExtendedStyle(LVS_EX_CHECKBOXES | LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP); + list.SetImageList(ui::Theme.GetImageList16().GetHandle()); + list.SetTheme(); + list.InsertColumn(0, 0, 0, 0, L"Name"); + list.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); + list.InsertGroup(0, L"General filters", true, false); + list.InsertGroup(1, L"Limited filters", true, false); + parent->feed_filters_.resize(Aggregator.filter_manager.filters.size()); + std::copy(Aggregator.filter_manager.filters.begin(), + Aggregator.filter_manager.filters.end(), + parent->feed_filters_.begin()); + parent->RefreshTorrentFilterList(list.GetWindowHandle()); + list.SetWindowHandle(nullptr); + // Initialize toolbar + win::Toolbar toolbar = GetDlgItem(IDC_TOOLBAR_FEED_FILTER); + toolbar.SetImageList(ui::Theme.GetImageList16().GetHandle(), 16, 16); + toolbar.SendMessage(TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_MIXEDBUTTONS); + // Add toolbar items + BYTE fsState1 = TBSTATE_ENABLED; + BYTE fsState2 = TBSTATE_INDETERMINATE; + toolbar.InsertButton(0, ui::kIcon16_Plus, 100, fsState1, 0, 0, nullptr, L"Add new filter..."); + toolbar.InsertButton(1, ui::kIcon16_Minus, 101, fsState2, 0, 1, nullptr, L"Delete filter"); + toolbar.InsertButton(2, 0, 0, 0, BTNS_SEP, 0, nullptr, nullptr); + toolbar.InsertButton(3, ui::kIcon16_ArrowUp, 103, fsState2, 0, 3, nullptr, L"Move up"); + toolbar.InsertButton(4, ui::kIcon16_ArrowDown, 104, fsState2, 0, 4, nullptr, L"Move down"); + toolbar.SetWindowHandle(nullptr); + break; + } + } + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL SettingsPage::OnCommand(WPARAM wParam, LPARAM lParam) { + switch (HIWORD(wParam)) { + case BN_CLICKED: { + switch (LOWORD(wParam)) { + // Edit format + case IDC_BUTTON_FORMAT_HTTP: + DlgFormat.mode = kFormatModeHttp; + DlgFormat.Create(IDD_FORMAT, parent->GetWindowHandle(), true); + return TRUE; + case IDC_BUTTON_FORMAT_MIRC: + DlgFormat.mode = kFormatModeMirc; + DlgFormat.Create(IDD_FORMAT, parent->GetWindowHandle(), true); + return TRUE; + case IDC_BUTTON_FORMAT_SKYPE: + DlgFormat.mode = kFormatModeSkype; + DlgFormat.Create(IDD_FORMAT, parent->GetWindowHandle(), true); + return TRUE; + case IDC_BUTTON_FORMAT_TWITTER: + DlgFormat.mode = kFormatModeTwitter; + DlgFormat.Create(IDD_FORMAT, parent->GetWindowHandle(), true); + return TRUE; + case IDC_BUTTON_FORMAT_BALLOON: + DlgFormat.mode = kFormatModeBalloon; + DlgFormat.Create(IDD_FORMAT, parent->GetWindowHandle(), true); + return TRUE; + + //////////////////////////////////////////////////////////////////////// + + // Add folders + case IDC_BUTTON_ADDFOLDER: { + std::wstring path; + if (win::BrowseForFolder(GetWindowHandle(), L"Please select a folder:", L"", path)) { + win::ListView list = GetDlgItem(IDC_LIST_FOLDERS_ROOT); + list.InsertItem(list.GetItemCount(), -1, ui::kIcon16_Folder, 0, nullptr, path.c_str(), 0); + list.SetSelectedItem(list.GetItemCount() - 1); + list.SetWindowHandle(nullptr); + } + return TRUE; + } + // Remove folders + case IDC_BUTTON_REMOVEFOLDER: { + win::ListView list = GetDlgItem(IDC_LIST_FOLDERS_ROOT); + while (list.GetSelectedCount() > 0) + list.DeleteItem(list.GetNextItem(-1, LVNI_SELECTED)); + EnableDlgItem(IDC_BUTTON_REMOVEFOLDER, FALSE); + list.SetWindowHandle(nullptr); + return TRUE; + } + + //////////////////////////////////////////////////////////////////////// + + // Clear cache + case IDC_BUTTON_CACHE_CLEAR: { + if (IsDlgButtonChecked(IDC_CHECK_CACHE1)) { + History.Clear(); + } + if (IsDlgButtonChecked(IDC_CHECK_CACHE2)) { + ImageDatabase.Clear(); + } + if (IsDlgButtonChecked(IDC_CHECK_CACHE3)) { + std::wstring path = taiga::GetPath(taiga::kPathFeed); + DeleteFolder(path); + } + parent->RefreshCache(); + CheckDlgButton(IDC_CHECK_CACHE1, FALSE); + CheckDlgButton(IDC_CHECK_CACHE2, FALSE); + CheckDlgButton(IDC_CHECK_CACHE3, FALSE); + EnableDlgItem(IDC_BUTTON_CACHE_CLEAR, FALSE); + return TRUE; + } + + //////////////////////////////////////////////////////////////////////// + + // Test DDE connection + case IDC_BUTTON_MIRC_TEST: { + std::wstring service; + GetDlgItemText(IDC_EDIT_MIRC_SERVICE, service); + Announcer.TestMircConnection(service); + return TRUE; + } + + //////////////////////////////////////////////////////////////////////// + + // Authorize Twitter + case IDC_BUTTON_TWITTER_AUTH: { + Twitter.RequestToken(); + return TRUE; + } + + //////////////////////////////////////////////////////////////////////// + + // Browse for torrent application + case IDC_BUTTON_TORRENT_BROWSE_APP: { + std::wstring current_directory = Taiga.GetCurrentDirectory(); + std::wstring path = win::BrowseForFile( + GetWindowHandle(), L"Please select a torrent application", + L"Executable files (*.exe)\0*.exe\0\0"); + if (current_directory != Taiga.GetCurrentDirectory()) + Taiga.SetCurrentDirectory(current_directory); + if (!path.empty()) + SetDlgItemText(IDC_EDIT_TORRENT_APP, path.c_str()); + return TRUE; + } + // Browse for torrent download path + case IDC_BUTTON_TORRENT_BROWSE_FOLDER: { + std::wstring path; + if (win::BrowseForFolder(GetWindowHandle(), L"Please select a folder:", L"", path)) + SetDlgItemText(IDC_COMBO_TORRENT_FOLDER, path.c_str()); + return TRUE; + } + // Enable/disable controls + case IDC_CHECK_CACHE1: + case IDC_CHECK_CACHE2: + case IDC_CHECK_CACHE3: { + bool enable = IsDlgButtonChecked(IDC_CHECK_CACHE1) || + IsDlgButtonChecked(IDC_CHECK_CACHE2) || + IsDlgButtonChecked(IDC_CHECK_CACHE3); + EnableDlgItem(IDC_BUTTON_CACHE_CLEAR, enable); + return TRUE; + } + case IDC_CHECK_TORRENT_AUTOCHECK: { + BOOL enable = IsDlgButtonChecked(LOWORD(wParam)); + EnableDlgItem(IDC_EDIT_TORRENT_INTERVAL, enable); + EnableDlgItem(IDC_SPIN_TORRENT_INTERVAL, enable); + EnableDlgItem(IDC_RADIO_TORRENT_NEW1, enable); + EnableDlgItem(IDC_RADIO_TORRENT_NEW2, enable); + return TRUE; + } + case IDC_CHECK_TORRENT_AUTOSETFOLDER: { + BOOL enable = IsDlgButtonChecked(LOWORD(wParam)); + EnableDlgItem(IDC_CHECK_TORRENT_AUTOUSEFOLDER, enable); + enable = enable && IsDlgButtonChecked(IDC_CHECK_TORRENT_AUTOUSEFOLDER); + EnableDlgItem(IDC_COMBO_TORRENT_FOLDER, enable); + EnableDlgItem(IDC_BUTTON_TORRENT_BROWSE_FOLDER, enable); + EnableDlgItem(IDC_CHECK_TORRENT_AUTOCREATEFOLDER, enable); + return TRUE; + } + case IDC_CHECK_TORRENT_AUTOUSEFOLDER: { + BOOL enable = IsDlgButtonChecked(LOWORD(wParam)); + EnableDlgItem(IDC_COMBO_TORRENT_FOLDER, enable); + EnableDlgItem(IDC_BUTTON_TORRENT_BROWSE_FOLDER, enable); + EnableDlgItem(IDC_CHECK_TORRENT_AUTOCREATEFOLDER, enable); + return TRUE; + } + // Enable/disable filters + case IDC_CHECK_TORRENT_FILTER: { + BOOL enable = IsDlgButtonChecked(LOWORD(wParam)); + EnableDlgItem(IDC_LIST_TORRENT_FILTER, enable); + EnableDlgItem(IDC_TOOLBAR_FEED_FILTER, enable); + return TRUE; + } + // Add global filter + case 100: { + DlgFeedFilter.filter.Reset(); + ExecuteAction(L"TorrentAddFilter", TRUE, reinterpret_cast(parent->GetWindowHandle())); + if (!DlgFeedFilter.filter.conditions.empty()) { + if (DlgFeedFilter.filter.name.empty()) + DlgFeedFilter.filter.name = Aggregator.filter_manager.CreateNameFromConditions(DlgFeedFilter.filter); + parent->feed_filters_.push_back(DlgFeedFilter.filter); + win::ListView list = GetDlgItem(IDC_LIST_TORRENT_FILTER); + parent->RefreshTorrentFilterList(list.GetWindowHandle()); + list.SetSelectedItem(list.GetItemCount() - 1); + list.SetWindowHandle(nullptr); + } + return TRUE; + } + // Remove global filter + case 101: { + win::ListView list = GetDlgItem(IDC_LIST_TORRENT_FILTER); + int item_index = list.GetNextItem(-1, LVNI_SELECTED); + FeedFilter* feed_filter = reinterpret_cast(list.GetItemParam(item_index)); + for (auto it = parent->feed_filters_.begin(); it != parent->feed_filters_.end(); ++it) { + if (feed_filter == &(*it)) { + parent->feed_filters_.erase(it); + parent->RefreshTorrentFilterList(list.GetWindowHandle()); + break; + } + } + list.SetWindowHandle(nullptr); + return TRUE; + } + // Move filter up + case 103: { + win::ListView list = GetDlgItem(IDC_LIST_TORRENT_FILTER); + int index = list.GetNextItem(-1, LVNI_SELECTED); + if (index > 0) { + iter_swap(parent->feed_filters_.begin() + index, + parent->feed_filters_.begin() + index - 1); + parent->RefreshTorrentFilterList(list.GetWindowHandle()); + list.SetSelectedItem(index - 1); + } + list.SetWindowHandle(nullptr); + return TRUE; + } + // Move filter down + case 104: { + win::ListView list = GetDlgItem(IDC_LIST_TORRENT_FILTER); + int index = list.GetNextItem(-1, LVNI_SELECTED); + if (index > -1 && index < list.GetItemCount() - 1) { + iter_swap(parent->feed_filters_.begin() + index, + parent->feed_filters_.begin() + index + 1); + parent->RefreshTorrentFilterList(list.GetWindowHandle()); + list.SetSelectedItem(index + 1); + } + list.SetWindowHandle(nullptr); + return TRUE; + } + + //////////////////////////////////////////////////////////////////////// + + // Check radio buttons + case IDC_RADIO_MIRC_CHANNEL1: + case IDC_RADIO_MIRC_CHANNEL2: + case IDC_RADIO_MIRC_CHANNEL3: + CheckRadioButton(IDC_RADIO_MIRC_CHANNEL1, IDC_RADIO_MIRC_CHANNEL3, LOWORD(wParam)); + EnableDlgItem(IDC_EDIT_MIRC_CHANNELS, LOWORD(wParam) == IDC_RADIO_MIRC_CHANNEL3); + return TRUE; + case IDC_RADIO_TORRENT_NEW1: + case IDC_RADIO_TORRENT_NEW2: + CheckRadioButton(IDC_RADIO_TORRENT_NEW1, IDC_RADIO_TORRENT_NEW2, LOWORD(wParam)); + return TRUE; + case IDC_RADIO_TORRENT_APP1: + case IDC_RADIO_TORRENT_APP2: + CheckRadioButton(IDC_RADIO_TORRENT_APP1, IDC_RADIO_TORRENT_APP2, LOWORD(wParam)); + EnableDlgItem(IDC_EDIT_TORRENT_APP, LOWORD(wParam) == IDC_RADIO_TORRENT_APP2); + EnableDlgItem(IDC_BUTTON_TORRENT_BROWSE_APP, LOWORD(wParam) == IDC_RADIO_TORRENT_APP2); + return TRUE; + } + break; + } + } + + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////////// + +void SettingsPage::OnDropFiles(HDROP hDropInfo) { + if (index != kSettingsPageLibraryFolders) + return; + + WCHAR szFileName[MAX_PATH + 1]; + UINT nFiles = DragQueryFile(hDropInfo, static_cast(-1), nullptr, 0); + win::ListView list = GetDlgItem(IDC_LIST_FOLDERS_ROOT); + for (UINT i = 0; i < nFiles; i++) { + ZeroMemory(szFileName, MAX_PATH + 1); + DragQueryFile(hDropInfo, i, (LPWSTR)szFileName, MAX_PATH + 1); + if (GetFileAttributes(szFileName) & FILE_ATTRIBUTE_DIRECTORY) { + list.InsertItem(list.GetItemCount(), -1, ui::kIcon16_Folder, 0, nullptr, szFileName, 0); + list.SetColumnWidth(0, LVSCW_AUTOSIZE_USEHEADER); + } + } + list.SetWindowHandle(nullptr); +} + +LRESULT SettingsPage::OnNotify(int idCtrl, LPNMHDR pnmh) { + switch (pnmh->code) { + // Header checkbox click + case HDN_ITEMCHANGED: { + win::ListView list = GetDlgItem(IDC_LIST_MEDIA); + if (pnmh->hwndFrom == list.GetHeader()) { + auto nmh = reinterpret_cast(pnmh); + if (nmh->pitem->mask & HDI_FORMAT) { + BOOL checked = nmh->pitem->fmt & HDF_CHECKED ? TRUE : FALSE; + for (size_t i = 0; i < MediaPlayers.items.size(); i++) + list.SetCheckState(i, checked); + } + } + list.SetWindowHandle(nullptr); + break; + } + + case NM_CLICK: { + switch (pnmh->idFrom) { + // Execute link + case IDC_LINK_ACCOUNT_HUMMINGBIRD: + case IDC_LINK_ACCOUNT_MAL: + case IDC_LINK_TWITTER: { + PNMLINK pNMLink = reinterpret_cast(pnmh); + ExecuteAction(pNMLink->item.szUrl); + return TRUE; + } + // Open themes folder + case IDC_LINK_THEMES: { + std::wstring theme_name; + GetDlgItemText(IDC_COMBO_THEME, theme_name); + std::wstring path = GetPathOnly(taiga::GetPath(taiga::kPathThemeCurrent)); + Execute(path); + return TRUE; + } + } + } + + // List item select + case LVN_ITEMCHANGED: { + LPNMLISTVIEW lplv = reinterpret_cast(pnmh); + if (lplv->hdr.hwndFrom == GetDlgItem(IDC_LIST_FOLDERS_ROOT)) { + EnableDlgItem(IDC_BUTTON_REMOVEFOLDER, ListView_GetSelectedCount(lplv->hdr.hwndFrom) > 0); + } else if (lplv->hdr.hwndFrom == GetDlgItem(IDC_LIST_TORRENT_FILTER)) { + win::ListView list = GetDlgItem(IDC_LIST_TORRENT_FILTER); + win::Toolbar toolbar = GetDlgItem(IDC_TOOLBAR_FEED_FILTER); + int index = list.GetNextItem(-1, LVNI_SELECTED); + int count = list.GetItemCount(); + toolbar.EnableButton(101, index > -1); + toolbar.EnableButton(103, index > 0); + toolbar.EnableButton(104, index > -1 && index < count - 1); + list.SetWindowHandle(nullptr); + toolbar.SetWindowHandle(nullptr); + } + break; + } + + // Text callback + case LVN_GETDISPINFO: { + NMLVDISPINFO* plvdi = reinterpret_cast(pnmh); + auto anime_item = AnimeDatabase.FindItem(static_cast(plvdi->item.lParam)); + if (!anime_item) break; + switch (plvdi->item.iSubItem) { + case 0: // Anime title + plvdi->item.pszText = const_cast(anime_item->GetTitle().data()); + break; + } + break; + } + case TBN_GETINFOTIP: { + if (pnmh->hwndFrom == GetDlgItem(IDC_TOOLBAR_FEED_FILTER)) { + win::Toolbar toolbar = GetDlgItem(IDC_TOOLBAR_FEED_FILTER); + NMTBGETINFOTIP* git = reinterpret_cast(pnmh); + git->cchTextMax = INFOTIPSIZE; + git->pszText = (LPWSTR)(toolbar.GetButtonTooltip(git->lParam)); + toolbar.SetWindowHandle(nullptr); + } + break; + } + + // Double click + case NM_DBLCLK: { + LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); + if (lpnmitem->iItem == -1) + break; + // Anime folders + if (lpnmitem->hdr.hwndFrom == GetDlgItem(IDC_LIST_FOLDERS_ROOT)) { + win::ListView list = lpnmitem->hdr.hwndFrom; + WCHAR buffer[MAX_PATH]; + list.GetItemText(lpnmitem->iItem, 0, buffer); + Execute(buffer); + list.SetWindowHandle(nullptr); + // Media players + } else if (lpnmitem->hdr.hwndFrom == GetDlgItem(IDC_LIST_MEDIA)) { + Execute(MediaPlayers.items[lpnmitem->iItem].GetPath()); + // Streaming media providers + } else if (lpnmitem->hdr.hwndFrom == GetDlgItem(IDC_LIST_STREAM_PROVIDER)) { + switch (lpnmitem->iItem) { + case 0: + ExecuteLink(L"http://www.animenewsnetwork.com/video/"); + break; + case 1: + ExecuteLink(L"http://www.crunchyroll.com"); + break; + case 2: + ExecuteLink(L"http://www.veoh.com"); + break; + case 3: + ExecuteLink(L"http://www.vizanime.com"); + break; + case 4: + ExecuteLink(L"http://www.youtube.com"); + break; + } + // Torrent filters + } else if (lpnmitem->hdr.hwndFrom == GetDlgItem(IDC_LIST_TORRENT_FILTER)) { + win::ListView list = lpnmitem->hdr.hwndFrom; + FeedFilter* feed_filter = reinterpret_cast(list.GetItemParam(lpnmitem->iItem)); + if (feed_filter) { + DlgFeedFilter.filter = *feed_filter; + DlgFeedFilter.Create(IDD_FEED_FILTER, parent->GetWindowHandle()); + if (!DlgFeedFilter.filter.conditions.empty()) { + *feed_filter = DlgFeedFilter.filter; + parent->RefreshTorrentFilterList(lpnmitem->hdr.hwndFrom); + list.SetSelectedItem(lpnmitem->iItem); + } + } + list.SetWindowHandle(nullptr); + } + return TRUE; + } + + // Right click + case NM_RCLICK: { + LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); + if (lpnmitem->iItem == -1) + break; + win::ListView list = lpnmitem->hdr.hwndFrom; + // Media players + if (lpnmitem->hdr.hwndFrom == GetDlgItem(IDC_LIST_MEDIA)) { + std::wstring answer = ui::Menus.Show(GetWindowHandle(), 0, 0, L"GenericList"); + for (int i = 0; i < list.GetItemCount(); i++) { + if (answer == L"SelectAll()") { + list.SetCheckState(i, TRUE); + } else if (answer == L"DeselectAll()") { + list.SetCheckState(i, FALSE); + } + } + } + list.SetWindowHandle(nullptr); + return TRUE; + } + } + + return 0; +} + +} // namespace ui diff --git a/dlg/dlg_settings_page.h b/src/ui/dlg/dlg_settings_page.h similarity index 52% rename from dlg/dlg_settings_page.h rename to src/ui/dlg/dlg_settings_page.h index 5840e6180..d268c7a5a 100644 --- a/dlg/dlg_settings_page.h +++ b/src/ui/dlg/dlg_settings_page.h @@ -1,67 +1,69 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_SETTINGS_PAGE_H -#define DLG_SETTINGS_PAGE_H - -#include "../std.h" -#include "../win32/win_dialog.h" - -enum SettingsPages { - PAGE_APP_BEHAVIOR = 1, - PAGE_APP_CONNECTION, - PAGE_APP_INTERFACE, - PAGE_APP_LIST, - PAGE_LIBRARY_CACHE, - PAGE_LIBRARY_FOLDERS, - PAGE_RECOGNITION_GENERAL, - PAGE_RECOGNITION_MEDIA, - PAGE_RECOGNITION_STREAM, - PAGE_SERVICES_MAL, - PAGE_SHARING_HTTP, - PAGE_SHARING_MESSENGER, - PAGE_SHARING_MIRC, - PAGE_SHARING_SKYPE, - PAGE_SHARING_TWITTER, - PAGE_TORRENTS_DISCOVERY, - PAGE_TORRENTS_DOWNLOADS, - PAGE_TORRENTS_FILTERS, - PAGE_COUNT -}; - -class SettingsDialog; - -// ============================================================================= - -class SettingsPage : public win32::Dialog { -public: - SettingsPage(); - virtual ~SettingsPage() {} - - BOOL OnCommand(WPARAM wParam, LPARAM lParam); - void OnDropFiles(HDROP hDropInfo); - BOOL OnInitDialog(); - LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); - - void Create(); - - int index; - SettingsDialog* parent; -}; - -#endif // DLG_SETTINGS_PAGE_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_SETTINGS_PAGE_H +#define TAIGA_UI_DLG_SETTINGS_PAGE_H + +#include "win/win_dialog.h" + +namespace ui { + +enum SettingsPages { + kSettingsPageAppBehavior = 1, + kSettingsPageAppConnection, + kSettingsPageAppInterface, + kSettingsPageAppList, + kSettingsPageLibraryCache, + kSettingsPageLibraryFolders, + kSettingsPageRecognitionGeneral, + kSettingsPageRecognitionMedia, + kSettingsPageRecognitionStream, + kSettingsPageServicesHummingbird, + kSettingsPageServicesMain, + kSettingsPageServicesMal, + kSettingsPageSharingHttp, + kSettingsPageSharingMirc, + kSettingsPageSharingSkype, + kSettingsPageSharingTwitter, + kSettingsPageTorrentsDiscovery, + kSettingsPageTorrentsDownloads, + kSettingsPageTorrentsFilters, + kSettingsPageCount +}; + +class SettingsDialog; + +class SettingsPage : public win::Dialog { +public: + SettingsPage(); + virtual ~SettingsPage() {} + + BOOL OnCommand(WPARAM wParam, LPARAM lParam); + void OnDropFiles(HDROP hDropInfo); + BOOL OnInitDialog(); + LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); + + void Create(); + + int index; + SettingsDialog* parent; +}; + +} // namespace ui + +#endif // TAIGA_UI_DLG_SETTINGS_PAGE_H \ No newline at end of file diff --git a/dlg/dlg_stats.cpp b/src/ui/dlg/dlg_stats.cpp similarity index 71% rename from dlg/dlg_stats.cpp rename to src/ui/dlg/dlg_stats.cpp index c409ef67c..7b774d6ea 100644 --- a/dlg/dlg_stats.cpp +++ b/src/ui/dlg/dlg_stats.cpp @@ -1,192 +1,191 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_stats.h" - -#include "../anime_db.h" -#include "../common.h" -#include "../gfx.h" -#include "../myanimelist.h" -#include "../resource.h" -#include "../stats.h" -#include "../string.h" -#include "../theme.h" - -class StatsDialog StatsDialog; - -// ============================================================================= - -BOOL StatsDialog::OnInitDialog() { - // Set new font for headers - for (int i = 0; i < 4; i++) { - SendDlgItemMessage(IDC_STATIC_HEADER1 + i, WM_SETFONT, - reinterpret_cast(UI.font_bold.Get()), FALSE); - } - - // Calculate and display statistics - Stats.CalculateAll(); - Refresh(); - - return TRUE; -} - -INT_PTR StatsDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - ResizeProc(hwnd, uMsg, wParam, lParam); - - switch (uMsg) { - case WM_CTLCOLORSTATIC: { - win32::Dc dc = reinterpret_cast(wParam); - dc.SetBkMode(TRANSPARENT); - dc.DetachDC(); - return reinterpret_cast(::GetSysColorBrush(COLOR_WINDOW)); - } - - case WM_DRAWITEM: { - // Draw score bars - if (wParam == IDC_STATIC_ANIME_STAT2) { - LPDRAWITEMSTRUCT dis = reinterpret_cast(lParam); - win32::Rect rect = dis->rcItem; - win32::Dc dc = dis->hDC; - - dc.FillRect(dis->rcItem, ::GetSysColor(COLOR_WINDOW)); - - int bar_height = GetTextHeight(dc.Get()); - int bar_max = rect.Width() * 3 / 4; - - for (int i = 10; i > 0; i--) { - if (i < 10) - rect.top += bar_height; - - if (Stats.score_distribution[i] > 0.0f) { - int bar_width = static_cast(bar_max * Stats.score_distribution[i]); - rect.bottom = rect.top + bar_height - 2; - rect.right = rect.left + bar_width; - dc.FillRect(rect, theme::COLOR_DARKBLUE); - } - - if (Stats.score_count[i] > 0.0f) { - wstring text = ToWstr(Stats.score_count[i]); - win32::Rect rect_text = rect; - rect_text.left = rect_text.right += 8; - rect_text.right = dis->rcItem.right; - dc.EditFont(nullptr, 7); - dc.SetBkMode(TRANSPARENT); - dc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); - dc.DrawText(text.c_str(), text.length(), rect_text, - DT_SINGLELINE | DT_VCENTER); - } - } - - dc.DetachDC(); - return TRUE; - } - break; - } - - case WM_ERASEBKGND: - return TRUE; - } - - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - -void StatsDialog::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { - win32::Dc dc = hdc; - win32::Rect rect; - - // Paint background - rect.Copy(lpps->rcPaint); - dc.FillRect(rect, ::GetSysColor(COLOR_WINDOW)); - - // Paint header lines - for (int i = 0; i < 4; i++) { - win32::Rect rect_header; - win32::Window header = GetDlgItem(IDC_STATIC_HEADER1 + i); - header.GetWindowRect(m_hWindow, &rect_header); - rect_header.top = rect_header.bottom + 3; - rect_header.bottom = rect_header.top + 1; - dc.FillRect(rect_header, ::GetSysColor(COLOR_ACTIVEBORDER)); - rect_header.Offset(0, 1); - dc.FillRect(rect_header, ::GetSysColor(COLOR_WINDOW)); - header.SetWindowHandle(nullptr); - } -} - -void StatsDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { - switch (uMsg) { - case WM_SIZE: { - win32::Rect rect; - rect.Set(0, 0, size.cx, size.cy); - rect.Inflate(-ScaleX(WIN_CONTROL_MARGIN) * 2, -ScaleY(WIN_CONTROL_MARGIN)); - - // Headers - for (int i = 0; i < 4; i++) { - win32::Rect rect_header; - win32::Window header = GetDlgItem(IDC_STATIC_HEADER1 + i); - header.GetWindowRect(m_hWindow, &rect_header); - rect_header.right = rect.right; - header.SetPosition(nullptr, rect_header); - header.SetWindowHandle(nullptr); - } - - // Redraw - InvalidateRect(); - } - } -} - -// ============================================================================= - -void StatsDialog::Refresh() { - if (!IsWindow()) return; - - // Anime list - wstring text; - text += ToWstr(Stats.anime_count) + L"\n"; - text += ToWstr(Stats.episode_count) + L"\n"; - text += Stats.life_spent_watching + L"\n"; - text += ToWstr(Stats.score_mean, 2) + L"\n"; - text += ToWstr(Stats.score_deviation, 2); - SetDlgItemText(IDC_STATIC_ANIME_STAT1, text.c_str()); - - // Score distribution - win32::Window window = GetDlgItem(IDC_STATIC_ANIME_STAT2); - win32::Rect rect; - window.GetWindowRect(GetWindowHandle(), &rect); - InvalidateRect(&rect); - window.SetWindowHandle(nullptr); - - // Database - text.clear(); - text += ToWstr(static_cast(AnimeDatabase.items.size())) + L"\n"; - text += ToWstr(Stats.image_count) + L" (" + ToSizeString(Stats.image_size) + L")\n"; - text += ToWstr(Stats.torrent_count) + L" (" + ToSizeString(Stats.torrent_size) + L")"; - SetDlgItemText(IDC_STATIC_ANIME_STAT3, text.c_str()); - - // Taiga - text.clear(); - text += ToWstr(Stats.connections_succeeded + Stats.connections_failed); - if (Stats.connections_failed > 0) - text += L" (" + ToWstr(Stats.connections_failed) + L" failed)"; - text += L"\n"; - text += ToDateString(Stats.uptime) + L"\n"; - text += ToWstr(Stats.tigers_harmed); - SetDlgItemText(IDC_STATIC_ANIME_STAT4, text.c_str()); -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/file.h" +#include "base/gfx.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "taiga/resource.h" +#include "taiga/stats.h" +#include "ui/dlg/dlg_stats.h" +#include "ui/theme.h" + +namespace ui { + +StatsDialog DlgStats; + +BOOL StatsDialog::OnInitDialog() { + // Set new font for headers + for (int i = 0; i < 4; i++) { + SendDlgItemMessage(IDC_STATIC_HEADER1 + i, WM_SETFONT, + reinterpret_cast(ui::Theme.GetBoldFont()), FALSE); + } + + // Calculate and display statistics + Stats.CalculateAll(); + Refresh(); + + return TRUE; +} + +INT_PTR StatsDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + ResizeProc(hwnd, uMsg, wParam, lParam); + + switch (uMsg) { + case WM_CTLCOLORSTATIC: { + win::Dc dc = reinterpret_cast(wParam); + dc.SetBkMode(TRANSPARENT); + dc.DetachDc(); + return reinterpret_cast(::GetSysColorBrush(COLOR_WINDOW)); + } + + case WM_DRAWITEM: { + // Draw score bars + if (wParam == IDC_STATIC_ANIME_STAT2) { + LPDRAWITEMSTRUCT dis = reinterpret_cast(lParam); + win::Rect rect = dis->rcItem; + win::Dc dc = dis->hDC; + + dc.FillRect(dis->rcItem, ::GetSysColor(COLOR_WINDOW)); + + int bar_height = GetTextHeight(dc.Get()); + int bar_max = rect.Width() * 3 / 4; + + for (int i = 10; i > 0; i--) { + if (i < 10) + rect.top += bar_height; + + if (Stats.score_distribution[i] > 0.0f) { + int bar_width = static_cast(bar_max * Stats.score_distribution[i]); + rect.bottom = rect.top + bar_height - 2; + rect.right = rect.left + bar_width; + dc.FillRect(rect, ui::kColorDarkBlue); + } + + if (Stats.score_count[i] > 0.0f) { + std::wstring text = ToWstr(Stats.score_count[i]); + win::Rect rect_text = rect; + rect_text.left = rect_text.right += 8; + rect_text.right = dis->rcItem.right; + dc.EditFont(nullptr, 7); + dc.SetBkMode(TRANSPARENT); + dc.SetTextColor(::GetSysColor(COLOR_GRAYTEXT)); + dc.DrawText(text.c_str(), text.length(), rect_text, + DT_SINGLELINE | DT_VCENTER); + } + } + + dc.DetachDc(); + return TRUE; + } + break; + } + + case WM_ERASEBKGND: + return TRUE; + } + + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + +void StatsDialog::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { + win::Dc dc = hdc; + win::Rect rect; + + // Paint background + rect.Copy(lpps->rcPaint); + dc.FillRect(rect, ::GetSysColor(COLOR_WINDOW)); + + // Paint header lines + for (int i = 0; i < 4; i++) { + win::Rect rect_header; + win::Window header = GetDlgItem(IDC_STATIC_HEADER1 + i); + header.GetWindowRect(GetWindowHandle(), &rect_header); + rect_header.top = rect_header.bottom + 3; + rect_header.bottom = rect_header.top + 1; + dc.FillRect(rect_header, ::GetSysColor(COLOR_ACTIVEBORDER)); + rect_header.Offset(0, 1); + dc.FillRect(rect_header, ::GetSysColor(COLOR_WINDOW)); + header.SetWindowHandle(nullptr); + } +} + +void StatsDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { + switch (uMsg) { + case WM_SIZE: { + win::Rect rect; + rect.Set(0, 0, size.cx, size.cy); + rect.Inflate(-ScaleX(win::kControlMargin) * 2, -ScaleY(win::kControlMargin)); + + // Headers + for (int i = 0; i < 4; i++) { + win::Rect rect_header; + win::Window header = GetDlgItem(IDC_STATIC_HEADER1 + i); + header.GetWindowRect(GetWindowHandle(), &rect_header); + rect_header.right = rect.right; + header.SetPosition(nullptr, rect_header); + header.SetWindowHandle(nullptr); + } + + // Redraw + InvalidateRect(); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void StatsDialog::Refresh() { + if (!IsWindow()) + return; + + // Anime list + std::wstring text; + text += ToWstr(Stats.anime_count) + L"\n"; + text += ToWstr(Stats.episode_count) + L"\n"; + text += Stats.life_spent_watching + L"\n"; + text += ToWstr(Stats.score_mean, 2) + L"\n"; + text += ToWstr(Stats.score_deviation, 2); + SetDlgItemText(IDC_STATIC_ANIME_STAT1, text.c_str()); + + // Score distribution + win::Window window = GetDlgItem(IDC_STATIC_ANIME_STAT2); + win::Rect rect; + window.GetWindowRect(GetWindowHandle(), &rect); + InvalidateRect(&rect); + window.SetWindowHandle(nullptr); + + // Database + text.clear(); + text += ToWstr(static_cast(AnimeDatabase.items.size())) + L"\n"; + text += ToWstr(Stats.image_count) + L" (" + ToSizeString(Stats.image_size) + L")\n"; + text += ToWstr(Stats.torrent_count) + L" (" + ToSizeString(Stats.torrent_size) + L")"; + SetDlgItemText(IDC_STATIC_ANIME_STAT3, text.c_str()); + + // Taiga + text.clear(); + text += ToWstr(Stats.connections_succeeded + Stats.connections_failed); + if (Stats.connections_failed > 0) + text += L" (" + ToWstr(Stats.connections_failed) + L" failed)"; + text += L"\n"; + text += ToDateString(Stats.uptime) + L"\n"; + text += ToWstr(Stats.tigers_harmed); + SetDlgItemText(IDC_STATIC_ANIME_STAT4, text.c_str()); +} + +} // namespace ui diff --git a/dlg/dlg_stats.h b/src/ui/dlg/dlg_stats.h similarity index 63% rename from dlg/dlg_stats.h rename to src/ui/dlg/dlg_stats.h index 6de8cb552..8bfb70fce 100644 --- a/dlg/dlg_stats.h +++ b/src/ui/dlg/dlg_stats.h @@ -1,44 +1,44 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_STATS_H -#define DLG_STATS_H - -#include "../std.h" -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" -#include "../win32/win_resizable.h" - -// ============================================================================= - -class StatsDialog : public win32::Dialog, public win32::Resizable { -public: - StatsDialog() {} - virtual ~StatsDialog() {} - - INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - BOOL OnInitDialog(); - void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); - void OnSize(UINT uMsg, UINT nType, SIZE size); - - void Refresh(); -}; - -extern class StatsDialog StatsDialog; - -#endif // DLG_STATS_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_STATS_H +#define TAIGA_UI_DLG_STATS_H + +#include "win/win_dialog.h" +#include "win/win_resizable.h" + +namespace ui { + +class StatsDialog : public win::Dialog, public win::Resizable { +public: + StatsDialog() {} + ~StatsDialog() {} + + INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + BOOL OnInitDialog(); + void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); + void OnSize(UINT uMsg, UINT nType, SIZE size); + + void Refresh(); +}; + +extern StatsDialog DlgStats; + +} // namespace ui + +#endif // TAIGA_UI_DLG_STATS_H \ No newline at end of file diff --git a/dlg/dlg_test_recognition.cpp b/src/ui/dlg/dlg_test_recognition.cpp similarity index 71% rename from dlg/dlg_test_recognition.cpp rename to src/ui/dlg/dlg_test_recognition.cpp index 3adb2ba3d..c2bbe730a 100644 --- a/dlg/dlg_test_recognition.cpp +++ b/src/ui/dlg/dlg_test_recognition.cpp @@ -1,219 +1,220 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_test_recognition.h" - -#include "../common.h" -#include "../recognition.h" -#include "../resource.h" -#include "../string.h" -#include "../taiga.h" -#include "../xml.h" - -RecognitionTestDialog RecognitionTest; - -// ============================================================================= - -BOOL RecognitionTestDialog::OnInitDialog() { - // Initialize - wstring file = Taiga.GetDataPath() + L"test\\recognition.xml"; - episodes_.clear(); - test_episodes_.clear(); - list_.DeleteAllItems(); - - // Load XML file - xml_document doc; - xml_parse_result result = doc.load_file(file.c_str()); - if (result.status != status_ok) { - ::MessageBox(NULL, L"Could not read recognition test file.", file.c_str(), MB_OK | MB_ICONERROR); - return FALSE; - } - - // Fill episode data - xml_node recognition = doc.child(L"recognition"); - for (xml_node file_node = recognition.child(L"file"); file_node; file_node = file_node.next_sibling(L"file")) { - EpisodeTest new_episode; - new_episode.audio_type = XML_ReadStrValue(file_node, L"audio"); - new_episode.checksum = XML_ReadStrValue(file_node, L"checksum"); - new_episode.extras = XML_ReadStrValue(file_node, L"extra"); - new_episode.file = XML_ReadStrValue(file_node, L"file"); - new_episode.format = XML_ReadStrValue(file_node, L"format"); - new_episode.group = XML_ReadStrValue(file_node, L"group"); - new_episode.name = XML_ReadStrValue(file_node, L"name"); - new_episode.number = XML_ReadStrValue(file_node, L"number"); - new_episode.priority = XML_ReadIntValue(file_node, L"priority"); - new_episode.resolution = XML_ReadStrValue(file_node, L"resolution"); - new_episode.title = XML_ReadStrValue(file_node, L"title"); - new_episode.version = XML_ReadStrValue(file_node, L"version"); - new_episode.video_type = XML_ReadStrValue(file_node, L"video"); - episodes_.push_back(new_episode); - } - - // Examine files - DWORD tick = GetTickCount(); - for (UINT i = 0; i < episodes_.size(); i++) { - EpisodeTest episode; - Meow.ExamineTitle(episodes_[i].file, episode, true, true, true, true, false); - episode.anime_id = i; - episode.priority = episodes_[i].priority; - test_episodes_.push_back(episode); - } - tick = GetTickCount() - tick; - - // Create list - list_.Attach(GetDlgItem(IDC_LIST_TEST_RECOGNITION)); - list_.InsertColumn(0, 400, 400, 0, L"File name"); - list_.InsertColumn(1, 200, 200, 0, L"Title"); - list_.InsertColumn(2, 100, 100, 0, L"Group"); - list_.InsertColumn(3, 55, 55, 0, L"Episode"); - list_.InsertColumn(4, 50, 50, 0, L"Version"); - list_.InsertColumn(5, 70, 70, 0, L"Audio"); - list_.InsertColumn(6, 70, 70, 0, L"Video"); - list_.InsertColumn(7, 80, 80, 0, L"Resolution"); - list_.InsertColumn(8, 80, 80, 0, L"Checksum"); - list_.InsertColumn(9, 100, 100, 0, L"Extra"); - list_.InsertColumn(10, 100, 100, 0, L"Name"); - list_.InsertColumn(11, 50, 50, 0, L"Format"); - - // Fill list - for (UINT i = 0; i < episodes_.size(); i++) { - list_.InsertItem(i, -1, -1, 0, NULL, test_episodes_[i].file.c_str(), - reinterpret_cast(&test_episodes_[i])); - list_.SetItem(i, 1, test_episodes_[i].title.c_str()); - list_.SetItem(i, 2, test_episodes_[i].group.c_str()); - list_.SetItem(i, 3, test_episodes_[i].number.c_str()); - list_.SetItem(i, 4, test_episodes_[i].version.c_str()); - list_.SetItem(i, 5, test_episodes_[i].audio_type.c_str()); - list_.SetItem(i, 6, test_episodes_[i].video_type.c_str()); - list_.SetItem(i, 7, test_episodes_[i].resolution.c_str()); - list_.SetItem(i, 8, test_episodes_[i].checksum.c_str()); - list_.SetItem(i, 9, test_episodes_[i].extras.c_str()); - list_.SetItem(i, 10, test_episodes_[i].name.c_str()); - list_.SetItem(i, 11, test_episodes_[i].format.c_str()); - } - list_.Sort(1, 1, LIST_SORTTYPE_DEFAULT, ListViewCompareProc); - - // Set title - int success_count = 0, total_items = episodes_.size(); - for (int i = 0; i < total_items; i++) { - if (episodes_[i].title == test_episodes_[i].title && - episodes_[i].number == test_episodes_[i].number) { - success_count++; - } - } - wstring title = L"Taiga Recognition Test"; - title += L" - Success rate: " + ToWstr(success_count) + L"/" + ToWstr(total_items); - title += L" (" + ToWstr(((float)success_count / (float)total_items) * 100.0f, 2) + L"%)"; - title += L" - Time: " + ToWstr(tick) + L" ms"; - SetText(title.c_str()); - - // Success - return TRUE; -} - -// ============================================================================= - -LRESULT RecognitionTestDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { - // ListView control - if (idCtrl == IDC_LIST_TEST_RECOGNITION) { - switch (pnmh->code) { - // Column click - case LVN_COLUMNCLICK: { - LPNMLISTVIEW lplv = reinterpret_cast(pnmh); - int order = 1; - if (lplv->iSubItem == list_.GetSortColumn()) order = list_.GetSortOrder() * -1; - int type = LIST_SORTTYPE_DEFAULT; - switch (lplv->iSubItem) { - case 3: - case 4: - type = LIST_SORTTYPE_NUMBER; - break; - } - list_.Sort(lplv->iSubItem, order, type, ListViewCompareProc); - break; - } - - // Custom draw - case NM_CUSTOMDRAW: { - LPNMLVCUSTOMDRAW pCD = reinterpret_cast(pnmh); - switch (pCD->nmcd.dwDrawStage) { - case CDDS_PREPAINT: - return CDRF_NOTIFYITEMDRAW; - case CDDS_ITEMPREPAINT: - return CDRF_NOTIFYSUBITEMDRAW; - case CDDS_PREERASE: - case CDDS_ITEMPREERASE: - return CDRF_NOTIFYPOSTERASE; - - case CDDS_ITEMPREPAINT | CDDS_SUBITEM: { - EpisodeTest* e = reinterpret_cast(pCD->nmcd.lItemlParam); - if (!e) return CDRF_NOTIFYPOSTPAINT; - #define CheckSubItem(e, t) \ - e->t == episodes_[e->anime_id].t ? RGB(230, 255, 230) : \ - e->t.empty() ? RGB(245, 255, 245) : RGB(255, 230, 230) - switch (pCD->iSubItem) { - // Title - case 1: pCD->clrTextBk = CheckSubItem(e, title); break; - // Group - case 2: pCD->clrTextBk = CheckSubItem(e, group); break; - // Episode - case 3: pCD->clrTextBk = CheckSubItem(e, number); break; - // Version - case 4: pCD->clrTextBk = CheckSubItem(e, version); break; - // Audio - case 5: pCD->clrTextBk = CheckSubItem(e, audio_type); break; - // Video - case 6: pCD->clrTextBk = CheckSubItem(e, video_type); break; - // Resolution - case 7: pCD->clrTextBk = CheckSubItem(e, resolution); break; - // Checksum - case 8: pCD->clrTextBk = CheckSubItem(e, checksum); break; - // Extra - case 9: pCD->clrTextBk = CheckSubItem(e, extras); break; - // Name - case 10: pCD->clrTextBk = CheckSubItem(e, name); break; - // Format - case 11: pCD->clrTextBk = CheckSubItem(e, format); break; - // Default - default: pCD->clrTextBk = GetSysColor(COLOR_WINDOW); - } - if (e->priority < 0) { - pCD->clrText = RGB(200, 200, 200); - } else if (e->priority > 0) { - pCD->clrText = RGB(255, 0, 0); - } - #undef CheckSubItem - return CDRF_NOTIFYPOSTPAINT; - } - } - } - } - } - - return 0; -} - -void RecognitionTestDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { - switch (uMsg) { - case WM_SIZE: { - list_.SetPosition(NULL, 0, 0, size.cx, size.cy); - } - } -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/string.h" +#include "base/xml.h" +#include "taiga/path.h" +#include "taiga/resource.h" +#include "track/recognition.h" +#include "ui/dlg/dlg_test_recognition.h" +#include "ui/list.h" + +namespace ui { + +RecognitionTestDialog DlgTestRecognition; + +BOOL RecognitionTestDialog::OnInitDialog() { + episodes_.clear(); + test_episodes_.clear(); + list_.DeleteAllItems(); + + xml_document document; + std::wstring path = taiga::GetPath(taiga::kPathTestRecognition); + xml_parse_result parse_result = document.load_file(path.c_str()); + + if (parse_result.status != pugi::status_ok) { + ::MessageBox(nullptr, L"Could not read recognition test file.", path.c_str(), + MB_OK | MB_ICONERROR); + return FALSE; + } + + // Fill episode data + xml_node recognition = document.child(L"recognition"); + for (xml_node file_node = recognition.child(L"file"); file_node; file_node = file_node.next_sibling(L"file")) { + EpisodeTest new_episode; + new_episode.audio_type = XmlReadStrValue(file_node, L"audio"); + new_episode.checksum = XmlReadStrValue(file_node, L"checksum"); + new_episode.extras = XmlReadStrValue(file_node, L"extra"); + new_episode.file = XmlReadStrValue(file_node, L"file"); + new_episode.format = XmlReadStrValue(file_node, L"format"); + new_episode.group = XmlReadStrValue(file_node, L"group"); + new_episode.name = XmlReadStrValue(file_node, L"name"); + new_episode.number = XmlReadStrValue(file_node, L"number"); + new_episode.priority = XmlReadIntValue(file_node, L"priority"); + new_episode.resolution = XmlReadStrValue(file_node, L"resolution"); + new_episode.title = XmlReadStrValue(file_node, L"title"); + new_episode.version = XmlReadStrValue(file_node, L"version"); + new_episode.video_type = XmlReadStrValue(file_node, L"video"); + episodes_.push_back(new_episode); + } + + // Examine files + DWORD tick = GetTickCount(); + for (UINT i = 0; i < episodes_.size(); i++) { + EpisodeTest episode; + Meow.ExamineTitle(episodes_[i].file, episode, true, true, true, true, false); + episode.anime_id = i; + episode.priority = episodes_[i].priority; + test_episodes_.push_back(episode); + } + tick = GetTickCount() - tick; + + // Create list + list_.Attach(GetDlgItem(IDC_LIST_TEST_RECOGNITION)); + list_.InsertColumn(0, 400, 400, 0, L"File name"); + list_.InsertColumn(1, 200, 200, 0, L"Title"); + list_.InsertColumn(2, 100, 100, 0, L"Group"); + list_.InsertColumn(3, 55, 55, 0, L"Episode"); + list_.InsertColumn(4, 50, 50, 0, L"Version"); + list_.InsertColumn(5, 70, 70, 0, L"Audio"); + list_.InsertColumn(6, 70, 70, 0, L"Video"); + list_.InsertColumn(7, 80, 80, 0, L"Resolution"); + list_.InsertColumn(8, 80, 80, 0, L"Checksum"); + list_.InsertColumn(9, 100, 100, 0, L"Extra"); + list_.InsertColumn(10, 100, 100, 0, L"Name"); + list_.InsertColumn(11, 50, 50, 0, L"Format"); + + // Fill list + for (UINT i = 0; i < episodes_.size(); i++) { + list_.InsertItem(i, -1, -1, 0, NULL, test_episodes_[i].file.c_str(), + reinterpret_cast(&test_episodes_[i])); + list_.SetItem(i, 1, test_episodes_[i].title.c_str()); + list_.SetItem(i, 2, test_episodes_[i].group.c_str()); + list_.SetItem(i, 3, test_episodes_[i].number.c_str()); + list_.SetItem(i, 4, test_episodes_[i].version.c_str()); + list_.SetItem(i, 5, test_episodes_[i].audio_type.c_str()); + list_.SetItem(i, 6, test_episodes_[i].video_type.c_str()); + list_.SetItem(i, 7, test_episodes_[i].resolution.c_str()); + list_.SetItem(i, 8, test_episodes_[i].checksum.c_str()); + list_.SetItem(i, 9, test_episodes_[i].extras.c_str()); + list_.SetItem(i, 10, test_episodes_[i].name.c_str()); + list_.SetItem(i, 11, test_episodes_[i].format.c_str()); + } + list_.Sort(1, 1, ui::kListSortDefault, ui::ListViewCompareProc); + + // Set title + int success_count = 0, total_items = episodes_.size(); + for (int i = 0; i < total_items; i++) { + if (episodes_[i].title == test_episodes_[i].title && + episodes_[i].number == test_episodes_[i].number) { + success_count++; + } + } + std::wstring title = L"Taiga Recognition Test"; + title += L" - Success rate: " + ToWstr(success_count) + L"/" + ToWstr(total_items); + title += L" (" + ToWstr(((float)success_count / (float)total_items) * 100.0f, 2) + L"%)"; + title += L" - Time: " + ToWstr(tick) + L" ms"; + SetText(title.c_str()); + + // Success + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// + +LRESULT RecognitionTestDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { + // ListView control + if (idCtrl == IDC_LIST_TEST_RECOGNITION) { + switch (pnmh->code) { + // Column click + case LVN_COLUMNCLICK: { + LPNMLISTVIEW lplv = reinterpret_cast(pnmh); + int order = 1; + if (lplv->iSubItem == list_.GetSortColumn()) + order = list_.GetSortOrder() * -1; + int type = ui::kListSortDefault; + switch (lplv->iSubItem) { + case 3: + case 4: + type = ui::kListSortNumber; + break; + } + list_.Sort(lplv->iSubItem, order, type, ui::ListViewCompareProc); + break; + } + + // Custom draw + case NM_CUSTOMDRAW: { + LPNMLVCUSTOMDRAW pCD = reinterpret_cast(pnmh); + switch (pCD->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + return CDRF_NOTIFYITEMDRAW; + case CDDS_ITEMPREPAINT: + return CDRF_NOTIFYSUBITEMDRAW; + case CDDS_PREERASE: + case CDDS_ITEMPREERASE: + return CDRF_NOTIFYPOSTERASE; + + case CDDS_ITEMPREPAINT | CDDS_SUBITEM: { + EpisodeTest* e = reinterpret_cast(pCD->nmcd.lItemlParam); + if (!e) + return CDRF_NOTIFYPOSTPAINT; + #define CheckSubItem(e, t) \ + e->t == episodes_[e->anime_id].t ? RGB(230, 255, 230) : \ + e->t.empty() ? RGB(245, 255, 245) : RGB(255, 230, 230) + switch (pCD->iSubItem) { + // Title + case 1: pCD->clrTextBk = CheckSubItem(e, title); break; + // Group + case 2: pCD->clrTextBk = CheckSubItem(e, group); break; + // Episode + case 3: pCD->clrTextBk = CheckSubItem(e, number); break; + // Version + case 4: pCD->clrTextBk = CheckSubItem(e, version); break; + // Audio + case 5: pCD->clrTextBk = CheckSubItem(e, audio_type); break; + // Video + case 6: pCD->clrTextBk = CheckSubItem(e, video_type); break; + // Resolution + case 7: pCD->clrTextBk = CheckSubItem(e, resolution); break; + // Checksum + case 8: pCD->clrTextBk = CheckSubItem(e, checksum); break; + // Extra + case 9: pCD->clrTextBk = CheckSubItem(e, extras); break; + // Name + case 10: pCD->clrTextBk = CheckSubItem(e, name); break; + // Format + case 11: pCD->clrTextBk = CheckSubItem(e, format); break; + // Default + default: pCD->clrTextBk = GetSysColor(COLOR_WINDOW); + } + if (e->priority < 0) { + pCD->clrText = RGB(200, 200, 200); + } else if (e->priority > 0) { + pCD->clrText = RGB(255, 0, 0); + } + #undef CheckSubItem + return CDRF_NOTIFYPOSTPAINT; + } + } + } + } + } + + return 0; +} + +void RecognitionTestDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { + switch (uMsg) { + case WM_SIZE: { + list_.SetPosition(NULL, 0, 0, size.cx, size.cy); + } + } +} + +} // namespace ui \ No newline at end of file diff --git a/dlg/dlg_test_recognition.h b/src/ui/dlg/dlg_test_recognition.h similarity index 61% rename from dlg/dlg_test_recognition.h rename to src/ui/dlg/dlg_test_recognition.h index c31fe1785..13c7f4869 100644 --- a/dlg/dlg_test_recognition.h +++ b/src/ui/dlg/dlg_test_recognition.h @@ -1,53 +1,54 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_TEST_RECOGNITOIN_H -#define DLG_TEST_RECOGNITOIN_H - -#include "../std.h" - -#include "../anime_episode.h" - -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" - -// ============================================================================= - -class RecognitionTestDialog : public win32::Dialog { -public: - RecognitionTestDialog() {}; - ~RecognitionTestDialog() {}; - - BOOL OnInitDialog(); - LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); - void OnSize(UINT uMsg, UINT nType, SIZE size); - -private: - class EpisodeTest : public anime::Episode { - public: - EpisodeTest() : priority(0) {} - int priority; - }; - vector episodes_, test_episodes_; - - win32::ListView list_; -}; - -extern RecognitionTestDialog RecognitionTest; - -#endif // DLG_TEST_RECOGNITOIN_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_TEST_RECOGNITOIN_H +#define TAIGA_UI_DLG_TEST_RECOGNITOIN_H + +#include + +#include "library/anime_episode.h" +#include "win/ctrl/win_ctrl.h" +#include "win/win_dialog.h" + +namespace ui { + +class RecognitionTestDialog : public win::Dialog { +public: + RecognitionTestDialog() {}; + ~RecognitionTestDialog() {}; + + BOOL OnInitDialog(); + LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); + void OnSize(UINT uMsg, UINT nType, SIZE size); + +private: + class EpisodeTest : public anime::Episode { + public: + EpisodeTest() : priority(0) {} + int priority; + }; + std::vector episodes_, test_episodes_; + + win::ListView list_; +}; + +extern RecognitionTestDialog DlgTestRecognition; + +} // namespace ui + +#endif // TAIGA_UI_DLG_TEST_RECOGNITOIN_H \ No newline at end of file diff --git a/dlg/dlg_torrent.cpp b/src/ui/dlg/dlg_torrent.cpp similarity index 64% rename from dlg/dlg_torrent.cpp rename to src/ui/dlg/dlg_torrent.cpp index 456dc5790..5d415bedf 100644 --- a/dlg/dlg_torrent.cpp +++ b/src/ui/dlg/dlg_torrent.cpp @@ -1,463 +1,465 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_main.h" -#include "dlg_settings.h" -#include "dlg_torrent.h" - -#include "../anime_db.h" -#include "../common.h" -#include "../feed.h" -#include "../gfx.h" -#include "../http.h" -#include "../resource.h" -#include "../settings.h" -#include "../string.h" -#include "../taiga.h" -#include "../theme.h" - -#include "../win32/win_gdi.h" - -class TorrentDialog TorrentDialog; - -// ============================================================================= - -BOOL TorrentDialog::OnInitDialog() { - // Set properties - SetSizeMin(470, 260); - - // Create list - list_.Attach(GetDlgItem(IDC_LIST_TORRENT)); - list_.EnableGroupView(true); - list_.SetExtendedStyle(LVS_EX_CHECKBOXES | LVS_EX_DOUBLEBUFFER | - LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP); - list_.SetImageList(UI.ImgList16.GetHandle()); - list_.SetTheme(); - - // Insert list columns - list_.InsertColumn(0, 240, 240, LVCFMT_LEFT, L"Anime title"); - list_.InsertColumn(1, 60, 60, LVCFMT_RIGHT, L"Episode"); - list_.InsertColumn(2, 120, 120, LVCFMT_LEFT, L"Group"); - list_.InsertColumn(3, 70, 70, LVCFMT_RIGHT, L"Size"); - list_.InsertColumn(4, 100, 100, LVCFMT_LEFT, L"Video"); - list_.InsertColumn(5, 250, 250, LVCFMT_LEFT, L"Description"); - list_.InsertColumn(6, 250, 250, LVCFMT_LEFT, L"File name"); - // Insert list groups - list_.InsertGroup(0, L"Anime"); - list_.InsertGroup(1, L"Batch"); - list_.InsertGroup(2, L"Other"); - - // Create main toolbar - toolbar_.Attach(GetDlgItem(IDC_TOOLBAR_TORRENT)); - toolbar_.SetImageList(UI.ImgList16.GetHandle(), 16, 16); - toolbar_.SendMessage(TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS | TBSTYLE_EX_MIXEDBUTTONS); - // Insert toolbar buttons - BYTE fsState = TBSTATE_ENABLED; - BYTE fsStyle = BTNS_AUTOSIZE | BTNS_SHOWTEXT; - toolbar_.InsertButton(0, ICON16_REFRESH, 100, fsState, fsStyle, 0, L"Check new torrents", NULL); - toolbar_.InsertButton(1, 0, 0, 0, BTNS_SEP, NULL, NULL, NULL); - toolbar_.InsertButton(2, ICON16_DOWNLOAD, 101, fsState, fsStyle, 0, L"Download marked torrents", NULL); - toolbar_.InsertButton(3, ICON16_CROSS, 102, fsState, fsStyle, 0, L"Discard all", NULL); - toolbar_.InsertButton(4, 0, 0, 0, BTNS_SEP, NULL, NULL, NULL); - toolbar_.InsertButton(5, ICON16_SETTINGS, 103, fsState, fsStyle, 0, L"Settings", NULL); - - // Create rebar - rebar_.Attach(GetDlgItem(IDC_REBAR_TORRENT)); - // Insert rebar bands - rebar_.InsertBand(NULL, 0, 0, 0, 0, 0, 0, 0, 0, - RBBIM_CHILD | RBBIM_CHILDSIZE | RBBIM_SIZE | RBBIM_STYLE, RBBS_NOGRIPPER); - rebar_.InsertBand(toolbar_.GetWindowHandle(), GetSystemMetrics(SM_CXSCREEN), 0, 0, 0, 0, 0, 0, - HIWORD(toolbar_.GetButtonSize()) + (HIWORD(toolbar_.GetPadding()) / 2), - RBBIM_CHILD | RBBIM_CHILDSIZE | RBBIM_SIZE | RBBIM_STYLE, RBBS_NOGRIPPER); - - // Refresh list - RefreshList(); - - return TRUE; -} - -// ============================================================================= - -INT_PTR TorrentDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - // Forward mouse wheel messages to the list - case WM_MOUSEWHEEL: { - return list_.SendMessage(uMsg, wParam, lParam); - } - } - - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - -BOOL TorrentDialog::OnCommand(WPARAM wParam, LPARAM lParam) { - Feed* feed = Aggregator.Get(FEED_CATEGORY_LINK); - if (!feed) return 0; - - // Toolbar - switch (LOWORD(wParam)) { - // Check new torrents - case 100: { - MainDialog.edit.SetText(L""); - feed->Check(Settings.RSS.Torrent.source); - /** - #ifdef _DEBUG - feed->Load(); - feed->ExamineData(); - RefreshList(); - #endif - /**/ - return TRUE; - } - // Download marked torrents - case 101: { - feed->Download(-1); - return TRUE; - } - // Discard marked torrents - case 102: { - for (int i = 0; i < list_.GetItemCount(); i++) { - if (list_.GetCheckState(i) == TRUE) { - FeedItem* feed_item = reinterpret_cast(list_.GetItemParam(i)); - if (feed_item) { - feed_item->state = FEEDITEM_DISCARDED_NORMAL; - list_.SetCheckState(i, FALSE); - Aggregator.file_archive.push_back(feed_item->title); - } - } - } - return TRUE; - } - // Settings - case 103: { - ExecuteAction(L"Settings", SECTION_TORRENTS, PAGE_TORRENTS_DISCOVERY); - return TRUE; - } - } - - return FALSE; -} - -LRESULT TorrentDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { - Feed* feed = Aggregator.Get(FEED_CATEGORY_LINK); - if (!feed) return 0; - - // ListView control - if (idCtrl == IDC_LIST_TORRENT) { - switch (pnmh->code) { - // Column click - case LVN_COLUMNCLICK: { - LPNMLISTVIEW lplv = (LPNMLISTVIEW)pnmh; - int order = 1; - if (lplv->iSubItem == list_.GetSortColumn()) order = list_.GetSortOrder() * -1; - switch (lplv->iSubItem) { - // Episode - case 1: - list_.Sort(lplv->iSubItem, order, LIST_SORTTYPE_NUMBER, ListViewCompareProc); - break; - // File size - case 3: - list_.Sort(lplv->iSubItem, order, LIST_SORTTYPE_FILESIZE, ListViewCompareProc); - break; - // Other columns - default: - list_.Sort(lplv->iSubItem, order, LIST_SORTTYPE_DEFAULT, ListViewCompareProc); - break; - } - break; - } - - // Check/uncheck - case LVN_ITEMCHANGED: { - if (!list_.IsVisible()) break; - LPNMLISTVIEW pnmv = reinterpret_cast(pnmh); - if (pnmv->uOldState != 0 && (pnmv->uNewState == 0x1000 || pnmv->uNewState == 0x2000)) { - int checked_count = 0; - for (int i = 0; i < list_.GetItemCount(); i++) { - if (list_.GetCheckState(i)) checked_count++; - } - if (checked_count == 1) { - MainDialog.ChangeStatus(L"Marked 1 torrent."); - } else { - MainDialog.ChangeStatus(L"Marked " + ToWstr(checked_count) + L" torrents."); - } - FeedItem* feed_item = reinterpret_cast(list_.GetItemParam(pnmv->iItem)); - if (feed_item) { - bool checked = list_.GetCheckState(pnmv->iItem) == TRUE; - feed_item->state = checked ? FEEDITEM_SELECTED : FEEDITEM_DISCARDED_NORMAL; - } - } - break; - } - - // Double click - case NM_DBLCLK: { - if (list_.GetSelectedCount() > 0) { - LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); - if (lpnmitem->iItem == -1) break; - FeedItem* feed_item = reinterpret_cast(list_.GetItemParam(lpnmitem->iItem)); - if (feed_item) { - feed->Download(feed_item->index); - } - } - break; - } - - // Right click - case NM_RCLICK: { - LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); - if (lpnmitem->iItem == -1) break; - FeedItem* feed_item = reinterpret_cast(list_.GetItemParam(lpnmitem->iItem)); - if (feed_item) { - wstring answer = UI.Menus.Show(m_hWindow, 0, 0, L"TorrentListRightClick"); - if (answer == L"DownloadTorrent") { - feed->Download(feed_item->index); - } else if (answer == L"Info") { - auto anime_id = feed_item->episode_data.anime_id; - if (anime_id) { - ExecuteAction(L"Info", 0, anime_id); - } else { - ExecuteAction(L"SearchAnime(" + feed_item->episode_data.title + L")"); - } - } else if (answer == L"DiscardTorrent") { - feed_item->state = FEEDITEM_DISCARDED_NORMAL; - list_.SetCheckState(lpnmitem->iItem, FALSE); - Aggregator.file_archive.push_back(feed_item->title); - } else if (answer == L"DiscardTorrents") { - auto anime_item = AnimeDatabase.FindItem(feed_item->episode_data.anime_id); - if (anime_item) { - for (int i = 0; i < list_.GetItemCount(); i++) { - feed_item = reinterpret_cast(list_.GetItemParam(i)); - if (feed_item && feed_item->episode_data.anime_id == anime_item->GetId()) { - feed_item->state = FEEDITEM_DISCARDED_NORMAL; - list_.SetCheckState(i, FALSE); - } - } - Aggregator.filter_manager.AddFilter( - FEED_FILTER_ACTION_DISCARD, FEED_FILTER_MATCH_ALL, FEED_FILTER_OPTION_DEFAULT, - true, L"Discard \"" + anime_item->GetTitle() + L"\""); - Aggregator.filter_manager.filters.back().AddCondition( - FEED_FILTER_ELEMENT_META_ID, FEED_FILTER_OPERATOR_EQUALS, - ToWstr(anime_item->GetId())); - } - } else if (answer == L"SelectFansub") { - int anime_id = feed_item->episode_data.anime_id; - wstring group_name = feed_item->episode_data.group; - if (anime_id > anime::ID_UNKNOWN && !group_name.empty()) { - for (int i = 0; i < list_.GetItemCount(); i++) { - feed_item = reinterpret_cast(list_.GetItemParam(i)); - if (feed_item && !IsEqual(feed_item->episode_data.group, group_name)) { - feed_item->state = FEEDITEM_DISCARDED_NORMAL; - list_.SetCheckState(i, FALSE); - } - } - anime::SetFansubFilter(anime_id, group_name); - } - } else if (answer == L"MoreTorrents") { - Search(Settings.RSS.Torrent.search_url, feed_item->episode_data.title); - } else if (answer == L"SearchMAL") { - ExecuteAction(L"SearchAnime(" + feed_item->episode_data.title + L")"); - } - } - break; - } - - // Custom draw - case NM_CUSTOMDRAW: { - LPNMLVCUSTOMDRAW pCD = reinterpret_cast(pnmh); - switch (pCD->nmcd.dwDrawStage) { - case CDDS_PREPAINT: - return CDRF_NOTIFYITEMDRAW; - case CDDS_ITEMPREPAINT: - return CDRF_NOTIFYSUBITEMDRAW; - case CDDS_PREERASE: - case CDDS_ITEMPREERASE: - return CDRF_NOTIFYPOSTERASE; - - case CDDS_ITEMPREPAINT | CDDS_SUBITEM: { - // Alternate background color - if ((pCD->nmcd.dwItemSpec % 2) && !list_.IsGroupViewEnabled()) - pCD->clrTextBk = ChangeColorBrightness(GetSysColor(COLOR_WINDOW), -0.03f); - FeedItem* feed_item = reinterpret_cast(pCD->nmcd.lItemlParam); - if (feed_item) { -#ifdef _DEBUG - // Change background color - switch (feed_item->state) { - case FEEDITEM_DISCARDED_NORMAL: - case FEEDITEM_DISCARDED_INACTIVE: - case FEEDITEM_DISCARDED_HIDDEN: - pCD->clrTextBk = theme::COLOR_LIGHTRED; - break; - case FEEDITEM_SELECTED: - pCD->clrTextBk = theme::COLOR_LIGHTGREEN; - break; - default: - pCD->clrTextBk = GetSysColor(COLOR_WINDOW); - break; - } -#endif - // Change text color - if (feed_item->state == FEEDITEM_DISCARDED_INACTIVE) { - pCD->clrText = GetSysColor(COLOR_GRAYTEXT); - } else if (feed_item->episode_data.new_episode) { - pCD->clrText = GetSysColor(pCD->iSubItem == 1 ? COLOR_HIGHLIGHT : COLOR_WINDOWTEXT); - } - } - return CDRF_NOTIFYPOSTPAINT; - } - } - } - } - } - - return 0; -} - -void TorrentDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { - switch (uMsg) { - case WM_SIZE: { - win32::Rect rcWindow; - rcWindow.Set(0, 0, size.cx, size.cy); - // Resize rebar - rebar_.SendMessage(WM_SIZE, 0, 0); - rcWindow.top += rebar_.GetBarHeight() + ScaleY(WIN_CONTROL_MARGIN / 2); - // Resize list - list_.SetPosition(NULL, rcWindow); - } - } -} - -// ============================================================================= - -void TorrentDialog::EnableInput(bool enable) { - // Enable/disable toolbar buttons - toolbar_.EnableButton(100, enable); - toolbar_.EnableButton(102, enable); - // Enable/disable list - list_.Enable(enable); -} - -void TorrentDialog::RefreshList() { - if (!IsWindow()) return; - Feed* feed = Aggregator.Get(FEED_CATEGORY_LINK); - if (!feed) return; - - // Hide list to avoid visual defects and gain performance - list_.Hide(); - list_.DeleteAllItems(); - - // Add items - for (auto it = feed->items.begin(); it != feed->items.end(); ++it) { - // Skip item if it was discarded and hidden - if (it->state == FEEDITEM_DISCARDED_HIDDEN) - continue; - - wstring title, number, video; - int group = TORRENT_ANIME, icon = StatusToIcon(0); - if (it->category == L"Batch" || - InStr(it->title, L"Vol.") > -1) { - group = TORRENT_BATCH; - } - if (!IsNumeric(it->episode_data.number)) { - if (it->episode_data.format.empty() || - IsEpisodeRange(it->episode_data.number)) { - group = TORRENT_BATCH; - } else { - group = TORRENT_OTHER; - } - } - auto anime_item = AnimeDatabase.FindItem(it->episode_data.anime_id); - if (anime_item) { - icon = StatusToIcon(anime_item->GetAiringStatus()); - title = anime_item->GetTitle(); - } else if (!it->episode_data.title.empty()) { - title = it->episode_data.title; - } else { - group = TORRENT_OTHER; - title = it->title; - } - vector numbers; - SplitEpisodeNumbers(it->episode_data.number, numbers); - number = JoinEpisodeNumbers(numbers); - if (!it->episode_data.version.empty()) { - number += L"v" + it->episode_data.version; - } - video = it->episode_data.video_type; - if (!it->episode_data.resolution.empty()) { - if (!video.empty()) video += L" "; - video += it->episode_data.resolution; - } - int index = list_.InsertItem(it - feed->items.begin(), - group, icon, 0, NULL, title.c_str(), - reinterpret_cast(&(*it))); - list_.SetItem(index, 1, number.c_str()); - list_.SetItem(index, 2, it->episode_data.group.c_str()); - list_.SetItem(index, 3, it->episode_data.file_size.c_str()); - list_.SetItem(index, 4, video.c_str()); - list_.SetItem(index, 5, it->description.c_str()); - list_.SetItem(index, 6, it->episode_data.file.c_str()); - list_.SetCheckState(index, it->state == FEEDITEM_SELECTED); - } - - // Show again - list_.Show(); - - // Set title - wstring title = L"Torrents"; - if (!feed->title.empty()) { - title = feed->title; - } else if (!feed->link.empty()) { - win32::Url url(feed->link); - title += L" (" + url.Host + L")"; - } - if (!feed->description.empty()) { - title += L" - " + feed->description; - } - SetText(title.c_str()); -} - -void TorrentDialog::Search(wstring url, int anime_id) { - auto anime_item = AnimeDatabase.FindItem(anime_id); - - if (!anime_item) - return; - - wstring title = anime_item->GetTitle(); - if (anime_item->GetUseAlternative() && - anime_item->UserSynonymsAvailable()) - title = anime_item->GetUserSynonyms().front(); - - Search(url, title); -} - -void TorrentDialog::Search(wstring url, wstring title) { - Feed* feed = Aggregator.Get(FEED_CATEGORY_LINK); - if (!feed) return; - - MainDialog.navigation.SetCurrentPage(SIDEBAR_ITEM_FEEDS); - MainDialog.edit.SetText(title); - MainDialog.ChangeStatus(L"Searching torrents for \"" + title + L"\"..."); - - Replace(url, L"%title%", title); - feed->Check(url); -} - -void TorrentDialog::SetTimerText(const wstring& text) { - toolbar_.SetButtonText(0, text.c_str()); -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/gfx.h" +#include "base/string.h" +#include "base/url.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "taiga/resource.h" +#include "taiga/script.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" +#include "ui/dlg/dlg_main.h" +#include "ui/dlg/dlg_settings.h" +#include "ui/dlg/dlg_torrent.h" +#include "ui/dialog.h" +#include "ui/list.h" +#include "ui/menu.h" +#include "ui/theme.h" +#include "ui/ui.h" + +namespace ui { + +TorrentDialog DlgTorrent; + +BOOL TorrentDialog::OnInitDialog() { + // Set properties + SetSizeMin(470, 260); + + // Create list + list_.Attach(GetDlgItem(IDC_LIST_TORRENT)); + list_.EnableGroupView(true); + list_.SetExtendedStyle( + LVS_EX_CHECKBOXES | LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP); + list_.SetImageList(ui::Theme.GetImageList16().GetHandle()); + list_.SetTheme(); + + // Insert list columns + list_.InsertColumn(0, 240, 240, LVCFMT_LEFT, L"Anime title"); + list_.InsertColumn(1, 60, 60, LVCFMT_RIGHT, L"Episode"); + list_.InsertColumn(2, 120, 120, LVCFMT_LEFT, L"Group"); + list_.InsertColumn(3, 70, 70, LVCFMT_RIGHT, L"Size"); + list_.InsertColumn(4, 100, 100, LVCFMT_LEFT, L"Video"); + list_.InsertColumn(5, 250, 250, LVCFMT_LEFT, L"Description"); + list_.InsertColumn(6, 250, 250, LVCFMT_LEFT, L"File name"); + // Insert list groups + list_.InsertGroup(0, L"Anime"); + list_.InsertGroup(1, L"Batch"); + list_.InsertGroup(2, L"Other"); + + // Create main toolbar + toolbar_.Attach(GetDlgItem(IDC_TOOLBAR_TORRENT)); + toolbar_.SetImageList(ui::Theme.GetImageList16().GetHandle(), 16, 16); + toolbar_.SendMessage(TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS | TBSTYLE_EX_MIXEDBUTTONS); + // Insert toolbar buttons + BYTE fsState = TBSTATE_ENABLED; + BYTE fsStyle = BTNS_AUTOSIZE | BTNS_SHOWTEXT; + toolbar_.InsertButton(0, ui::kIcon16_Refresh, 100, fsState, fsStyle, 0, L"Check new torrents", NULL); + toolbar_.InsertButton(1, 0, 0, 0, BTNS_SEP, NULL, NULL, NULL); + toolbar_.InsertButton(2, ui::kIcon16_Download, 101, fsState, fsStyle, 0, L"Download marked torrents", NULL); + toolbar_.InsertButton(3, ui::kIcon16_Cross, 102, fsState, fsStyle, 0, L"Discard all", NULL); + toolbar_.InsertButton(4, 0, 0, 0, BTNS_SEP, NULL, NULL, NULL); + toolbar_.InsertButton(5, ui::kIcon16_Settings, 103, fsState, fsStyle, 0, L"Settings", NULL); + + // Create rebar + rebar_.Attach(GetDlgItem(IDC_REBAR_TORRENT)); + // Insert rebar bands + rebar_.InsertBand(NULL, 0, 0, 0, 0, 0, 0, 0, 0, + RBBIM_CHILD | RBBIM_CHILDSIZE | RBBIM_SIZE | RBBIM_STYLE, RBBS_NOGRIPPER); + rebar_.InsertBand(toolbar_.GetWindowHandle(), GetSystemMetrics(SM_CXSCREEN), 0, 0, 0, 0, 0, 0, + HIWORD(toolbar_.GetButtonSize()) + (HIWORD(toolbar_.GetPadding()) / 2), + RBBIM_CHILD | RBBIM_CHILDSIZE | RBBIM_SIZE | RBBIM_STYLE, RBBS_NOGRIPPER); + + // Refresh list + RefreshList(); + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// + +INT_PTR TorrentDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + // Forward mouse wheel messages to the list + case WM_MOUSEWHEEL: { + return list_.SendMessage(uMsg, wParam, lParam); + } + } + + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + +BOOL TorrentDialog::OnCommand(WPARAM wParam, LPARAM lParam) { + Feed* feed = Aggregator.Get(kFeedCategoryLink); + if (!feed) + return 0; + + // Toolbar + switch (LOWORD(wParam)) { + // Check new torrents + case 100: { + DlgMain.edit.SetText(L""); + feed->Check(Settings[taiga::kTorrent_Discovery_Source]); + /** + #ifdef _DEBUG + feed->Load(); + feed->ExamineData(); + RefreshList(); + #endif + /**/ + return TRUE; + } + // Download marked torrents + case 101: { + feed->Download(-1); + return TRUE; + } + // Discard marked torrents + case 102: { + for (int i = 0; i < list_.GetItemCount(); i++) { + if (list_.GetCheckState(i) == TRUE) { + FeedItem* feed_item = reinterpret_cast(list_.GetItemParam(i)); + if (feed_item) { + feed_item->state = kFeedItemDiscardedNormal; + list_.SetCheckState(i, FALSE); + Aggregator.file_archive.push_back(feed_item->title); + } + } + } + return TRUE; + } + // Settings + case 103: { + ShowDlgSettings(kSettingsSectionTorrents, kSettingsPageTorrentsDiscovery); + return TRUE; + } + } + + return FALSE; +} + +LRESULT TorrentDialog::OnNotify(int idCtrl, LPNMHDR pnmh) { + Feed* feed = Aggregator.Get(kFeedCategoryLink); + if (!feed) + return 0; + + // ListView control + if (idCtrl == IDC_LIST_TORRENT) { + switch (pnmh->code) { + // Column click + case LVN_COLUMNCLICK: { + LPNMLISTVIEW lplv = (LPNMLISTVIEW)pnmh; + int order = 1; + if (lplv->iSubItem == list_.GetSortColumn()) order = list_.GetSortOrder() * -1; + switch (lplv->iSubItem) { + // Episode + case 1: + list_.Sort(lplv->iSubItem, order, ui::kListSortNumber, ui::ListViewCompareProc); + break; + // File size + case 3: + list_.Sort(lplv->iSubItem, order, ui::kListSortFileSize, ui::ListViewCompareProc); + break; + // Other columns + default: + list_.Sort(lplv->iSubItem, order, ui::kListSortDefault, ui::ListViewCompareProc); + break; + } + break; + } + + // Check/uncheck + case LVN_ITEMCHANGED: { + if (!list_.IsVisible()) break; + LPNMLISTVIEW pnmv = reinterpret_cast(pnmh); + if (pnmv->uOldState != 0 && (pnmv->uNewState == 0x1000 || pnmv->uNewState == 0x2000)) { + int checked_count = 0; + for (int i = 0; i < list_.GetItemCount(); i++) { + if (list_.GetCheckState(i)) checked_count++; + } + if (checked_count == 1) { + DlgMain.ChangeStatus(L"Marked 1 torrent."); + } else { + DlgMain.ChangeStatus(L"Marked " + ToWstr(checked_count) + L" torrents."); + } + FeedItem* feed_item = reinterpret_cast(list_.GetItemParam(pnmv->iItem)); + if (feed_item) { + bool checked = list_.GetCheckState(pnmv->iItem) == TRUE; + feed_item->state = checked ? kFeedItemSelected : kFeedItemDiscardedNormal; + } + } + break; + } + + // Double click + case NM_DBLCLK: { + if (list_.GetSelectedCount() > 0) { + LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); + if (lpnmitem->iItem == -1) break; + FeedItem* feed_item = reinterpret_cast(list_.GetItemParam(lpnmitem->iItem)); + if (feed_item) + feed->Download(feed_item->index); + } + break; + } + + // Right click + case NM_RCLICK: { + LPNMITEMACTIVATE lpnmitem = reinterpret_cast(pnmh); + if (lpnmitem->iItem == -1) break; + FeedItem* feed_item = reinterpret_cast(list_.GetItemParam(lpnmitem->iItem)); + if (feed_item) { + std::wstring answer = ui::Menus.Show(GetWindowHandle(), 0, 0, L"TorrentListRightClick"); + if (answer == L"DownloadTorrent") { + feed->Download(feed_item->index); + } else if (answer == L"Info") { + auto anime_id = feed_item->episode_data.anime_id; + if (anime_id) { + ShowDlgAnimeInfo(anime_id); + } else { + ExecuteAction(L"SearchAnime(" + feed_item->episode_data.title + L")"); + } + } else if (answer == L"DiscardTorrent") { + feed_item->state = kFeedItemDiscardedNormal; + list_.SetCheckState(lpnmitem->iItem, FALSE); + Aggregator.file_archive.push_back(feed_item->title); + } else if (answer == L"DiscardTorrents") { + auto anime_item = AnimeDatabase.FindItem(feed_item->episode_data.anime_id); + if (anime_item) { + for (int i = 0; i < list_.GetItemCount(); i++) { + feed_item = reinterpret_cast(list_.GetItemParam(i)); + if (feed_item && feed_item->episode_data.anime_id == anime_item->GetId()) { + feed_item->state = kFeedItemDiscardedNormal; + list_.SetCheckState(i, FALSE); + } + } + Aggregator.filter_manager.AddFilter( + kFeedFilterActionDiscard, kFeedFilterMatchAll, kFeedFilterOptionDefault, + true, L"Discard \"" + anime_item->GetTitle() + L"\""); + Aggregator.filter_manager.filters.back().AddCondition( + kFeedFilterElement_Meta_Id, kFeedFilterOperator_Equals, + ToWstr(anime_item->GetId())); + } + } else if (answer == L"SelectFansub") { + int anime_id = feed_item->episode_data.anime_id; + std::wstring group_name = feed_item->episode_data.group; + if (anime_id > anime::ID_UNKNOWN && !group_name.empty()) { + for (int i = 0; i < list_.GetItemCount(); i++) { + feed_item = reinterpret_cast(list_.GetItemParam(i)); + if (feed_item && !IsEqual(feed_item->episode_data.group, group_name)) { + feed_item->state = kFeedItemDiscardedNormal; + list_.SetCheckState(i, FALSE); + } + } + anime::SetFansubFilter(anime_id, group_name); + } + } else if (answer == L"MoreTorrents") { + Search(Settings[taiga::kTorrent_Discovery_SearchUrl], feed_item->episode_data.title); + } else if (answer == L"SearchService") { + ExecuteAction(L"SearchAnime(" + feed_item->episode_data.title + L")"); + } + } + break; + } + + // Custom draw + case NM_CUSTOMDRAW: { + LPNMLVCUSTOMDRAW pCD = reinterpret_cast(pnmh); + switch (pCD->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + return CDRF_NOTIFYITEMDRAW; + case CDDS_ITEMPREPAINT: + return CDRF_NOTIFYSUBITEMDRAW; + case CDDS_PREERASE: + case CDDS_ITEMPREERASE: + return CDRF_NOTIFYPOSTERASE; + + case CDDS_ITEMPREPAINT | CDDS_SUBITEM: { + // Alternate background color + if ((pCD->nmcd.dwItemSpec % 2) && !list_.IsGroupViewEnabled()) + pCD->clrTextBk = ChangeColorBrightness(GetSysColor(COLOR_WINDOW), -0.03f); + FeedItem* feed_item = reinterpret_cast(pCD->nmcd.lItemlParam); + if (feed_item) { + if (Taiga.debug_mode) { + // Change background color + switch (feed_item->state) { + case kFeedItemDiscardedNormal: + case kFeedItemDiscardedInactive: + case kFeedItemDiscardedHidden: + pCD->clrTextBk = ui::kColorLightRed; + break; + case kFeedItemSelected: + pCD->clrTextBk = ui::kColorLightGreen; + break; + default: + pCD->clrTextBk = GetSysColor(COLOR_WINDOW); + break; + } + } + // Change text color + if (feed_item->state == kFeedItemDiscardedInactive) { + pCD->clrText = GetSysColor(COLOR_GRAYTEXT); + } else if (feed_item->episode_data.new_episode) { + pCD->clrText = GetSysColor(pCD->iSubItem == 1 ? COLOR_HIGHLIGHT : COLOR_WINDOWTEXT); + } + } + return CDRF_NOTIFYPOSTPAINT; + } + } + } + } + } + + return 0; +} + +void TorrentDialog::OnSize(UINT uMsg, UINT nType, SIZE size) { + switch (uMsg) { + case WM_SIZE: { + win::Rect rcWindow; + rcWindow.Set(0, 0, size.cx, size.cy); + // Resize rebar + rebar_.SendMessage(WM_SIZE, 0, 0); + rcWindow.top += rebar_.GetBarHeight() + ScaleY(win::kControlMargin / 2); + // Resize list + list_.SetPosition(NULL, rcWindow); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void TorrentDialog::EnableInput(bool enable) { + // Enable/disable toolbar buttons + toolbar_.EnableButton(100, enable); + toolbar_.EnableButton(102, enable); + // Enable/disable list + list_.Enable(enable); +} + +void TorrentDialog::RefreshList() { + if (!IsWindow()) return; + Feed* feed = Aggregator.Get(kFeedCategoryLink); + if (!feed) + return; + + // Hide list to avoid visual defects and gain performance + list_.Hide(); + list_.DeleteAllItems(); + + // Add items + for (auto it = feed->items.begin(); it != feed->items.end(); ++it) { + // Skip item if it was discarded and hidden + if (it->state == kFeedItemDiscardedHidden) + continue; + + std::wstring title, number, video; + int group = kTorrentCategoryAnime; + int icon = StatusToIcon(anime::kUnknownStatus); + if (it->category == L"Batch" || + InStr(it->title, L"Vol.") > -1) { + group = kTorrentCategoryBatch; + } + if (!IsNumeric(it->episode_data.number)) { + if (it->episode_data.format.empty() || + anime::IsEpisodeRange(it->episode_data.number)) { + group = kTorrentCategoryBatch; + } else { + group = kTorrentCategoryOther; + } + } + auto anime_item = AnimeDatabase.FindItem(it->episode_data.anime_id); + if (anime_item) { + icon = StatusToIcon(anime_item->GetAiringStatus()); + title = anime_item->GetTitle(); + } else if (!it->episode_data.title.empty()) { + title = it->episode_data.title; + } else { + group = kTorrentCategoryOther; + title = it->title; + } + std::vector numbers; + anime::SplitEpisodeNumbers(it->episode_data.number, numbers); + number = anime::JoinEpisodeNumbers(numbers); + if (!it->episode_data.version.empty()) { + number += L"v" + it->episode_data.version; + } + video = it->episode_data.video_type; + if (!it->episode_data.resolution.empty()) { + if (!video.empty()) video += L" "; + video += it->episode_data.resolution; + } + int index = list_.InsertItem(it - feed->items.begin(), + group, icon, 0, NULL, title.c_str(), + reinterpret_cast(&(*it))); + list_.SetItem(index, 1, number.c_str()); + list_.SetItem(index, 2, it->episode_data.group.c_str()); + list_.SetItem(index, 3, it->episode_data.file_size.c_str()); + list_.SetItem(index, 4, video.c_str()); + list_.SetItem(index, 5, it->description.c_str()); + list_.SetItem(index, 6, it->episode_data.file.c_str()); + list_.SetCheckState(index, it->state == kFeedItemSelected); + } + + // Show again + list_.Show(); +} + +void TorrentDialog::Search(std::wstring url, int anime_id) { + auto anime_item = AnimeDatabase.FindItem(anime_id); + + if (!anime_item) + return; + + std::wstring title = anime_item->GetTitle(); + if (anime_item->GetUseAlternative() && + anime_item->UserSynonymsAvailable()) + title = anime_item->GetUserSynonyms().front(); + + Search(url, title); +} + +void TorrentDialog::Search(std::wstring url, std::wstring title) { + Feed* feed = Aggregator.Get(kFeedCategoryLink); + if (!feed) + return; + + DlgMain.navigation.SetCurrentPage(kSidebarItemFeeds); + DlgMain.edit.SetText(title); + DlgMain.ChangeStatus(L"Searching torrents for \"" + title + L"\"..."); + + Replace(url, L"%title%", title); + feed->Check(url); +} + +void TorrentDialog::SetTimer(int ticks) { + if (!IsWindow()) + return; + + std::wstring text = L"Check new torrents"; + + if (Settings.GetBool(taiga::kTorrent_Discovery_AutoCheckEnabled) && + Settings.GetInt(taiga::kTorrent_Discovery_AutoCheckInterval) > 0) { + text += L" [" + ToTimeString(ticks) + L"]"; + } + + toolbar_.SetButtonText(0, text.c_str()); +} + +} // namespace ui diff --git a/dlg/dlg_torrent.h b/src/ui/dlg/dlg_torrent.h similarity index 59% rename from dlg/dlg_torrent.h rename to src/ui/dlg/dlg_torrent.h index 2c5607188..c52e5babd 100644 --- a/dlg/dlg_torrent.h +++ b/src/ui/dlg/dlg_torrent.h @@ -1,54 +1,56 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_TORRENT_H -#define DLG_TORRENT_H - -#include "../std.h" -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" - -// ============================================================================= - -class TorrentDialog : public win32::Dialog { -public: - TorrentDialog() {}; - virtual ~TorrentDialog() {}; - - INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - BOOL OnCommand(WPARAM wParam, LPARAM lParam); - BOOL OnInitDialog(); - LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); - void OnSize(UINT uMsg, UINT nType, SIZE size); - -public: - void EnableInput(bool enable = true); - void RefreshList(); - void Search(wstring url, int anime_id); - void Search(wstring url, wstring title); - void SetTimerText(const wstring& text); - -private: - win32::ListView list_; - win32::Rebar rebar_; - win32::Toolbar toolbar_; -}; - -extern class TorrentDialog TorrentDialog; - -#endif // DLG_TORRENT_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_TORRENT_H +#define TAIGA_UI_DLG_TORRENT_H + +#include + +#include "win/ctrl/win_ctrl.h" +#include "win/win_dialog.h" + +namespace ui { + +class TorrentDialog : public win::Dialog { +public: + TorrentDialog() {}; + ~TorrentDialog() {}; + + INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + BOOL OnCommand(WPARAM wParam, LPARAM lParam); + BOOL OnInitDialog(); + LRESULT OnNotify(int idCtrl, LPNMHDR pnmh); + void OnSize(UINT uMsg, UINT nType, SIZE size); + + void EnableInput(bool enable = true); + void RefreshList(); + void Search(std::wstring url, int anime_id); + void Search(std::wstring url, std::wstring title); + void SetTimer(int ticks); + +private: + win::ListView list_; + win::Rebar rebar_; + win::Toolbar toolbar_; +}; + +extern TorrentDialog DlgTorrent; + +} // namespace ui + +#endif // TAIGA_UI_DLG_TORRENT_H \ No newline at end of file diff --git a/dlg/dlg_update.cpp b/src/ui/dlg/dlg_update.cpp similarity index 65% rename from dlg/dlg_update.cpp rename to src/ui/dlg/dlg_update.cpp index 77fb1467b..e4b20ae68 100644 --- a/dlg/dlg_update.cpp +++ b/src/ui/dlg/dlg_update.cpp @@ -1,100 +1,94 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "../std.h" - -#include "dlg_main.h" -#include "dlg_update.h" - -#include "../common.h" -#include "../http.h" -#include "../resource.h" -#include "../taiga.h" -#include "../theme.h" - -#include "../win32/win_gdi.h" -#include "../win32/win_taskdialog.h" - -class UpdateDialog UpdateDialog; - -// ============================================================================= - -UpdateDialog::UpdateDialog() { - RegisterDlgClass(L"TaigaUpdateW"); -} - -BOOL UpdateDialog::OnInitDialog() { - // Set icon - SetIconLarge(IDI_MAIN); - SetIconSmall(IDI_MAIN); - - // Create default brushes and fonts - UI.CreateBrushes(); - UI.CreateFonts(GetDC()); - - // Set title - SetText(APP_TITLE); - - // Set progress text - SetDlgItemText(IDC_STATIC_UPDATE_PROGRESS, L"Checking updates..."); - // Set progress bar - progressbar.Attach(GetDlgItem(IDC_PROGRESS_UPDATE)); - progressbar.SetMarquee(true); - - // Check updates - Taiga.Updater.Check(Taiga); - - return TRUE; -} - -INT_PTR UpdateDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_CTLCOLORSTATIC: { - win32::Dc dc = reinterpret_cast(wParam); - dc.SetBkMode(TRANSPARENT); - dc.DetachDC(); - return reinterpret_cast(::GetSysColorBrush(COLOR_WINDOW)); - } - } - - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - -BOOL UpdateDialog::OnDestroy() { - // Clean up - Taiga.Updater.client.Cleanup(); - - if (Taiga.Updater.IsRestartRequired()) { - if (MainDialog.IsWindow()) { - MainDialog.PostMessage(WM_CLOSE); - } else { - Taiga.Uninitialize(); - } - } else { - // Create/activate main window - ExecuteAction(L"MainDialog"); - } - - return TRUE; -} - -void UpdateDialog::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { - // Paint background - win32::Dc dc = hdc; - dc.FillRect(lpps->rcPaint, ::GetSysColor(COLOR_WINDOW)); -} \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "taiga/resource.h" +#include "taiga/taiga.h" +#include "ui/dlg/dlg_main.h" +#include "ui/dlg/dlg_update.h" +#include "ui/dialog.h" +#include "ui/theme.h" + +namespace ui { + +UpdateDialog DlgUpdate; + +UpdateDialog::UpdateDialog() { + RegisterDlgClass(L"TaigaUpdateW"); +} + +BOOL UpdateDialog::OnInitDialog() { + // Set icon + SetIconLarge(IDI_MAIN); + SetIconSmall(IDI_MAIN); + + // Create default fonts + Theme.CreateFonts(GetDC()); + + // Set title + SetText(TAIGA_APP_TITLE); + + // Set progress text + SetDlgItemText(IDC_STATIC_UPDATE_PROGRESS, L"Checking updates..."); + // Set progress bar + progressbar.Attach(GetDlgItem(IDC_PROGRESS_UPDATE)); + progressbar.SetMarquee(true); + + // Check updates + Taiga.Updater.Check(); + + return TRUE; +} + +INT_PTR UpdateDialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_CTLCOLORSTATIC: { + win::Dc dc = reinterpret_cast(wParam); + dc.SetBkMode(TRANSPARENT); + dc.DetachDc(); + return reinterpret_cast(::GetSysColorBrush(COLOR_WINDOW)); + } + } + + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + +BOOL UpdateDialog::OnDestroy() { + // Clean up + Taiga.Updater.Cancel(); + + if (Taiga.Updater.IsRestartRequired()) { + if (DlgMain.IsWindow()) { + DlgMain.PostMessage(WM_CLOSE); + } else { + Taiga.Uninitialize(); + } + } else { + // Create/activate main window + ShowDialog(kDialogMain); + } + + return TRUE; +} + +void UpdateDialog::OnPaint(HDC hdc, LPPAINTSTRUCT lpps) { + // Paint background + win::Dc dc = hdc; + dc.FillRect(lpps->rcPaint, ::GetSysColor(COLOR_WINDOW)); +} + +} // namespace ui diff --git a/dlg/dlg_update.h b/src/ui/dlg/dlg_update.h similarity index 62% rename from dlg/dlg_update.h rename to src/ui/dlg/dlg_update.h index 75b4694be..139cad352 100644 --- a/dlg/dlg_update.h +++ b/src/ui/dlg/dlg_update.h @@ -1,44 +1,44 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DLG_UPDATE_H -#define DLG_UPDATE_H - -#include "../std.h" -#include "../win32/win_control.h" -#include "../win32/win_dialog.h" - -// ============================================================================= - -class UpdateDialog : public win32::Dialog { -public: - UpdateDialog(); - virtual ~UpdateDialog() {} - - INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - BOOL OnDestroy(); - BOOL OnInitDialog(); - void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); - -public: - win32::ProgressBar progressbar; -}; - -extern class UpdateDialog UpdateDialog; - -#endif // DLG_UPDATE_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_UPDATE_H +#define TAIGA_UI_DLG_UPDATE_H + +#include "win/ctrl/win_ctrl.h" +#include "win/win_dialog.h" + +namespace ui { + +class UpdateDialog : public win::Dialog { +public: + UpdateDialog(); + ~UpdateDialog() {} + + INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + BOOL OnDestroy(); + BOOL OnInitDialog(); + void OnPaint(HDC hdc, LPPAINTSTRUCT lpps); + + win::ProgressBar progressbar; +}; + +extern UpdateDialog DlgUpdate; + +} // namespace ui + +#endif // TAIGA_UI_DLG_UPDATE_H \ No newline at end of file diff --git a/src/ui/dlg/dlg_update_new.cpp b/src/ui/dlg/dlg_update_new.cpp new file mode 100644 index 000000000..94f97ea5a --- /dev/null +++ b/src/ui/dlg/dlg_update_new.cpp @@ -0,0 +1,83 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "base/string.h" +#include "taiga/resource.h" +#include "taiga/taiga.h" +#include "ui/dlg/dlg_update.h" +#include "ui/dlg/dlg_update_new.h" +#include "ui/theme.h" + +namespace ui { + +NewUpdateDialog DlgUpdateNew; + +BOOL NewUpdateDialog::OnInitDialog() { + // Set icon + SetIconLarge(IDI_MAIN); + SetIconSmall(IDI_MAIN); + + // Set main text + SendDlgItemMessage(IDC_STATIC_UPDATE_TITLE, WM_SETFONT, + reinterpret_cast(Theme.GetHeaderFont()), TRUE); + SetDlgItemText(IDC_STATIC_UPDATE_TITLE, + L"A new version of " TAIGA_APP_NAME L" is available!"); + + // Set details text + std::wstring text = L"Current version: " + std::wstring(Taiga.version); + SetDlgItemText(IDC_STATIC_UPDATE_DETAILS, text.c_str()); + + // Set changelog text + std::wstring changelog = + L"{\\rtf1\\ansi\\deff0" + L"{\\fonttbl" + L"{\\f0 Segoe UI;}" + L"}" + L"\\deflang1024\\fs18"; + foreach_(item, Taiga.Updater.items) { + base::SemanticVersion item_version(item->guid); + if (item_version > Taiga.version) { + changelog += L"\\b Version " + item->guid + L"\\b0\\line "; + std::wstring description = item->description; + Replace(description, L"\n", L"\\line ", true); + changelog += description + L"\\line\\line "; + } + } + changelog += L"}"; + win::RichEdit rich_edit(GetDlgItem(IDC_RICHEDIT_UPDATE)); + rich_edit.SetTextEx(WstrToStr(changelog)); + rich_edit.SetWindowHandle(nullptr); + + return TRUE; +} + +void NewUpdateDialog::OnOK() { + EndDialog(IDOK); + + if (!Taiga.Updater.Download()) + DlgUpdate.PostMessage(WM_CLOSE); +} + +void NewUpdateDialog::OnCancel() { + EndDialog(IDCANCEL); + + DlgUpdate.PostMessage(WM_CLOSE); +} + +} // namespace ui \ No newline at end of file diff --git a/src/ui/dlg/dlg_update_new.h b/src/ui/dlg/dlg_update_new.h new file mode 100644 index 000000000..2f9a5d43f --- /dev/null +++ b/src/ui/dlg/dlg_update_new.h @@ -0,0 +1,40 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_DLG_UPDATE_NEW_H +#define TAIGA_UI_DLG_UPDATE_NEW_H + +#include "win/win_dialog.h" + +namespace ui { + +class NewUpdateDialog : public win::Dialog { +public: + NewUpdateDialog() {} + ~NewUpdateDialog() {} + + BOOL OnInitDialog(); + void OnOK(); + void OnCancel(); +}; + +extern NewUpdateDialog DlgUpdateNew; + +} // namespace ui + +#endif // TAIGA_UI_DLG_UPDATE_NEW_H \ No newline at end of file diff --git a/src/ui/list.cpp b/src/ui/list.cpp new file mode 100644 index 000000000..c1f5e2b03 --- /dev/null +++ b/src/ui/list.cpp @@ -0,0 +1,341 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "list.h" + +#include "base/comparable.h" +#include "base/string.h" +#include "base/time.h" +#include "library/anime_db.h" +#include "library/anime_util.h" +#include "taiga/settings.h" + +#include "win/ctrl/win_ctrl.h" + +namespace ui { + +int SortAsFileSize(LPCWSTR str1, LPCWSTR str2) { + UINT64 size[2] = {1, 1}; + + for (size_t i = 0; i < 2; i++) { + std::wstring value = i == 0 ? str1 : str2; + std::wstring unit; + + TrimRight(value, L".\r"); + EraseChars(value, L" "); + + if (value.length() >= 2) { + for (auto it = value.rbegin(); it != value.rend(); ++it) { + if (IsNumeric(*it)) + break; + unit.insert(unit.begin(), *it); + } + value.resize(value.length() - unit.length()); + Trim(unit); + } + + int index = InStr(value, L"."); + if (index > -1) { + int length = value.substr(index + 1).length(); + if (length <= 2) + value.append(2 - length, '0'); + EraseChars(value, L"."); + } else { + value.append(2, '0'); + } + + if (IsEqual(unit, L"KB")) { + size[i] *= 1000; + } else if (IsEqual(unit, L"KiB")) { + size[i] *= 1024; + } else if (IsEqual(unit, L"MB")) { + size[i] *= 1000 * 1000; + } else if (IsEqual(unit, L"MiB")) { + size[i] *= 1024 * 1024; + } else if (IsEqual(unit, L"GB")) { + size[i] *= 1000 * 1000 * 1000; + } else if (IsEqual(unit, L"GiB")) { + size[i] *= 1024 * 1024 * 1024; + } + + size[i] *= _wtoi(value.c_str()); + } + + if (size[0] > size[1]) { + return base::kGreaterThan; + } else if (size[0] < size[1]) { + return base::kLessThan; + } + + return base::kEqualTo; +} + +int SortAsNumber(LPCWSTR str1, LPCWSTR str2) { + int num1 = _wtoi(str1); + int num2 = _wtoi(str2); + + if (num1 > num2) { + return base::kGreaterThan; + } else if (num1 < num2) { + return base::kLessThan; + } + + return base::kEqualTo; +} + +int SortAsText(LPCWSTR str1, LPCWSTR str2) { + return lstrcmpi(str1, str2); +} + +//////////////////////////////////////////////////////////////////////////////// + +int SortListByAiringStatus(const anime::Item& item1, const anime::Item& item2) { + int status1 = item1.GetAiringStatus(); + int status2 = item2.GetAiringStatus(); + + if (status1 != status2) + return status2 > status1 ? base::kGreaterThan : base::kLessThan; + + return base::kEqualTo; +} + +int SortListByDateStart(const anime::Item& item1, const anime::Item& item2) { + Date date1 = item1.GetDateStart(); + Date date2 = item2.GetDateStart(); + + if (date1 != date2) { + if (!date1.year) + date1.year = static_cast(-1); // Hello. + if (!date2.year) + date2.year = static_cast(-1); // We come from the future. + if (!date1.month) + date1.month = 12; + if (!date2.month) + date2.month = 12; + if (!date1.day) + date1.day = 31; + if (!date2.day) + date2.day = 31; + + return date2 > date1 ? base::kGreaterThan : base::kLessThan; + } + + return base::kEqualTo; +} + +int SortListByEpisodeCount(const anime::Item& item1, const anime::Item& item2) { + if (item1.GetEpisodeCount() > item2.GetEpisodeCount()) { + return base::kGreaterThan; + } else if (item1.GetEpisodeCount() < item2.GetEpisodeCount()) { + return base::kLessThan; + } + + return base::kEqualTo; +} + +int SortListByLastUpdated(const anime::Item& item1, const anime::Item& item2) { + time_t time1 = _wtoi64(item1.GetMyLastUpdated().c_str()); + time_t time2 = _wtoi64(item2.GetMyLastUpdated().c_str()); + + if (time1 > time2) { + return base::kGreaterThan; + } else if (time1 < time2) { + return base::kLessThan; + } + + return base::kEqualTo; +} + +int SortListByPopularity(const anime::Item& item1, const anime::Item& item2) { + int val1 = 0; + int val2 = 0; + + if (!item1.GetPopularity().empty()) + val1 = _wtoi(item1.GetPopularity().substr(1).c_str()); + if (!item2.GetPopularity().empty()) + val2 = _wtoi(item2.GetPopularity().substr(1).c_str()); + + if (val2 == 0) { + return base::kLessThan; + } else if (val1 == 0) { + return base::kGreaterThan; + } else if (val1 > val2) { + return base::kGreaterThan; + } else if (val1 < val2) { + return base::kLessThan; + } + + return base::kEqualTo; +} + +int SortListByProgress(const anime::Item& item1, const anime::Item& item2) { + int total1 = item1.GetEpisodeCount(); + int total2 = item2.GetEpisodeCount(); + int watched1 = item1.GetMyLastWatchedEpisode(); + int watched2 = item2.GetMyLastWatchedEpisode(); + bool available1 = item1.IsNewEpisodeAvailable(); + bool available2 = item2.IsNewEpisodeAvailable(); + + if (available1 && !available2) { + return base::kLessThan; + } else if (!available1 && available2) { + return base::kGreaterThan; + } else if (total1 && total2) { + float ratio1 = static_cast(watched1) / static_cast(total1); + float ratio2 = static_cast(watched2) / static_cast(total2); + if (ratio1 > ratio2) { + return base::kLessThan; + } else if (ratio1 < ratio2) { + return base::kGreaterThan; + } else { + if (total1 > total2) { + return base::kLessThan; + } else if (total1 < total2) { + return base::kGreaterThan; + } + } + } else { + if (watched1 > watched2) { + return base::kLessThan; + } else if (watched1 < watched2) { + return base::kGreaterThan; + } else { + if (total1 > total2) { + return base::kLessThan; + } else if (total1 < total2) { + return base::kGreaterThan; + } + } + } + + return base::kEqualTo; +} + +int SortListByScore(const anime::Item& item1, const anime::Item& item2) { + return lstrcmpi(item1.GetScore().c_str(), item2.GetScore().c_str()); +} + +int SortListByTitle(const anime::Item& item1, const anime::Item& item2) { + if (Settings.GetBool(taiga::kApp_List_DisplayEnglishTitles)) { + return CompareStrings(item1.GetEnglishTitle(true), + item2.GetEnglishTitle(true)); + } else { + return CompareStrings(item1.GetTitle(), item2.GetTitle()); + } +} + +int SortListBySeason(const anime::Item& item1, const anime::Item& item2, + int order) { + auto season1 = anime::TranslateDateToSeason(item1.GetDateStart()); + auto season2 = anime::TranslateDateToSeason(item2.GetDateStart()); + + if (season1 != season2) + return season2 > season1 ? base::kGreaterThan : base::kLessThan; + + if (item1.GetAiringStatus() != item2.GetAiringStatus()) + return SortListByAiringStatus(item1, item2); + + return SortListByTitle(item1, item2) * order; +} + +//////////////////////////////////////////////////////////////////////////////// + +int SortList(int type, LPCWSTR str1, LPCWSTR str2) { + switch (type) { + case kListSortDefault: + default: + return SortAsText(str1, str2); + case kListSortFileSize: + return SortAsFileSize(str1, str2); + case kListSortNumber: + return SortAsNumber(str1, str2); + } + + return base::kEqualTo; +} + +int SortList(int type, int order, int id1, int id2) { + auto item1 = AnimeDatabase.FindItem(id1); + auto item2 = AnimeDatabase.FindItem(id2); + + if (item1 && item2) { + switch (type) { + case kListSortDateStart: + return SortListByDateStart(*item1, *item2); + case kListSortEpisodeCount: + return SortListByEpisodeCount(*item1, *item2); + case kListSortLastUpdated: + return SortListByLastUpdated(*item1, *item2); + case kListSortPopularity: + return SortListByPopularity(*item1, *item2); + case kListSortProgress: + return SortListByProgress(*item1, *item2); + case kListSortScore: + return SortListByScore(*item1, *item2); + case kListSortSeason: + return SortListBySeason(*item1, *item2, order); + case kListSortTitle: + return SortListByTitle(*item1, *item2); + } + } + + return base::kEqualTo; +} + +//////////////////////////////////////////////////////////////////////////////// + +int CALLBACK ListViewCompareProc(LPARAM lParam1, LPARAM lParam2, + LPARAM lParamSort) { + if (!lParamSort) + return base::kEqualTo; + + win::ListView* list = reinterpret_cast(lParamSort); + int return_value = base::kEqualTo; + + switch (list->GetSortType()) { + case kListSortDefault: + case kListSortFileSize: + case kListSortNumber: + default: { + WCHAR str1[MAX_PATH]; + WCHAR str2[MAX_PATH]; + list->GetItemText(lParam1, list->GetSortColumn(), str1); + list->GetItemText(lParam2, list->GetSortColumn(), str2); + return_value = SortList(list->GetSortType(), str1, str2); + break; + } + + case kListSortDateStart: + case kListSortEpisodeCount: + case kListSortLastUpdated: + case kListSortPopularity: + case kListSortProgress: + case kListSortScore: + case kListSortSeason: + case kListSortTitle: { + return_value = SortList(list->GetSortType(), list->GetSortOrder(), + static_cast(list->GetItemParam(lParam1)), + static_cast(list->GetItemParam(lParam2))); + break; + } + } + + return return_value * list->GetSortOrder(); +} + +} // namespace ui \ No newline at end of file diff --git a/src/ui/list.h b/src/ui/list.h new file mode 100644 index 000000000..7fccb9704 --- /dev/null +++ b/src/ui/list.h @@ -0,0 +1,45 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_LIST_H +#define TAIGA_UI_LIST_H + +#include + +namespace ui { + +enum ListSortType { + kListSortDefault, + kListSortFileSize, + kListSortNumber, + kListSortDateStart, + kListSortEpisodeCount, + kListSortLastUpdated, + kListSortPopularity, + kListSortProgress, + kListSortScore, + kListSortSeason, + kListSortTitle +}; + +int CALLBACK ListViewCompareProc(LPARAM lParam1, LPARAM lParam2, + LPARAM lParamSort); + +} // namespace ui + +#endif // TAIGA_UI_LIST_H \ No newline at end of file diff --git a/src/ui/menu.cpp b/src/ui/menu.cpp new file mode 100644 index 000000000..ea593c64c --- /dev/null +++ b/src/ui/menu.cpp @@ -0,0 +1,346 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/foreach.h" +#include "base/string.h" +#include "base/xml.h" +#include "library/anime_db.h" +#include "library/anime_episode.h" +#include "sync/sync.h" +#include "taiga/settings.h" +#include "ui/menu.h" + +#include "dlg/dlg_main.h" +#include "dlg/dlg_season.h" + +namespace ui { + +MenuList Menus; + +void MenuList::Load() { + menu_list_.menus.clear(); + + std::wstring menu_resource; + ReadStringFromResource(L"IDR_MENU", L"DATA", menu_resource); + + xml_document document; + xml_parse_result parse_result = document.load(menu_resource.data()); + + xml_node menus = document.child(L"menus"); + + foreach_xmlnode_(menu, menus, L"menu") { + menu_list_.Create(menu.attribute(L"name").value(), + menu.attribute(L"type").value()); + foreach_xmlnode_(item, menu, L"item") { + menu_list_.menus.back().CreateItem( + item.attribute(L"action").value(), + item.attribute(L"name").value(), + item.attribute(L"sub").value(), + item.attribute(L"checked").as_bool(), + item.attribute(L"default").as_bool(), + !item.attribute(L"disabled").as_bool(), + item.attribute(L"column").as_bool(), + item.attribute(L"radio").as_bool()); + } + } +} + +std::wstring MenuList::Show(HWND hwnd, int x, int y, LPCWSTR lpName) { + return menu_list_.Show(hwnd, x, y, lpName); +} + +void MenuList::UpdateAnime(const anime::Item* anime_item) { + if (!anime_item) + return; + if (!anime_item->IsInList()) + return; + + // Edit > Score + auto menu = menu_list_.FindMenu(L"EditScore"); + if (menu) { + foreach_(it, menu->items) { + it->checked = false; + it->def = false; + } + int item_index = anime_item->GetMyScore(); + if (item_index < static_cast(menu->items.size())) { + menu->items[item_index].checked = true; + menu->items[item_index].def = true; + } + } + // Edit > Status + menu = menu_list_.FindMenu(L"EditStatus"); + if (menu) { + foreach_(it, menu->items) { + it->checked = false; + it->def = false; + } + int item_index = anime_item->GetMyStatus(); + if (item_index - 1 < static_cast(menu->items.size())) { + menu->items[item_index - 1].checked = true; + menu->items[item_index - 1].def = true; + } + } + + // Play + menu = menu_list_.FindMenu(L"RightClick"); + if (menu) { + for (int i = static_cast(menu->items.size()) - 1; i > 0; i--) { + if (menu->items[i].type == win::kMenuItemSeparator) { + // Clear items + menu->items.resize(i + 1); + // Play episode + if (anime_item->GetEpisodeCount() != 1) { + menu->CreateItem(L"", L"Play episode", L"PlayEpisode"); + } + // Play last episode + if (anime_item->GetMyLastWatchedEpisode() > 0) { + menu->CreateItem(L"PlayLast()", + L"Play last episode (#" + + ToWstr(anime_item->GetMyLastWatchedEpisode()) + L")"); + } + // Play next episode + if (anime_item->GetEpisodeCount() == 0 || + anime_item->GetMyLastWatchedEpisode() < anime_item->GetEpisodeCount()) { + menu->CreateItem(L"PlayNext()", + L"Play next episode (#" + + ToWstr(anime_item->GetMyLastWatchedEpisode() + 1) + L")"); + } + // Play random episode + if (anime_item->GetEpisodeCount() != 1) { + menu->CreateItem(L"PlayRandom()", L"Play random episode"); + } + break; + } + } + } + + // Play > Episode + menu = menu_list_.FindMenu(L"PlayEpisode"); + if (menu) { + // Clear menu + menu->items.clear(); + + // Add episode numbers + int count_max = 0; + int count_column = 0; + if (anime_item->GetEpisodeCount() > 0) { + count_max = anime_item->GetEpisodeCount(); + } else { + count_max = anime_item->GetEpisodeCount(); + if (count_max == 0) { + count_max = anime_item->GetMyLastWatchedEpisode() + 1; + } + } + for (int i = 1; i <= count_max; i++) { + count_column = count_max % 12 == 0 ? 12 : 13; + if (count_max > 52) + count_column *= 2; + menu->CreateItem(L"PlayEpisode(" + ToWstr(i) + L")", + L"#" + ToWstr(i), + L"", + i <= anime_item->GetMyLastWatchedEpisode(), + false, + true, + (i > 1) && (i % count_column == 1), + false); + } + } +} + +void MenuList::UpdateAnnounce() { + // List > Announce current episode + auto menu = menu_list_.FindMenu(L"List"); + if (menu) { + foreach_(it, menu->items) { + if (it->submenu == L"Announce") { + it->enabled = CurrentEpisode.anime_id > 0; + break; + } + } + } +} + +void MenuList::UpdateExternalLinks() { + auto menu = menu_list_.FindMenu(L"ExternalLinks"); + if (menu) { + // Clear menu + menu->items.clear(); + + std::vector lines; + Split(Settings[taiga::kApp_Interface_ExternalLinks], L"\r\n", lines); + foreach_(line, lines) { + if (IsEqual(*line, L"-")) { + // Add separator + menu->CreateItem(); + } else { + std::vector content; + Split(*line, L"|", content); + if (content.size() > 1) { + menu->CreateItem(L"URL(" + content.at(1) + L")", content.at(0)); + } + } + } + } +} + +void MenuList::UpdateFolders() { + auto menu = menu_list_.FindMenu(L"Folders"); + if (menu) { + // Clear menu + menu->items.clear(); + + if (!Settings.root_folders.empty()) { + // Add folders + foreach_(it, Settings.root_folders) { + menu->CreateItem(L"Execute(" + *it + L")", *it); + } + // Add separator + menu->CreateItem(); + } + + // Add default item + menu->CreateItem(L"AddFolder()", L"Add new folder..."); + } +} + +void MenuList::UpdateSearchList(bool enabled) { + auto menu = menu_list_.FindMenu(L"SearchList"); + if (menu) { + // Add to list + foreach_(it, menu->items) { + if (it->submenu == L"AddToList") { + it->enabled = enabled; + break; + } + } + } +} + +void MenuList::UpdateSeasonList(bool enabled) { + auto menu = menu_list_.FindMenu(L"SeasonList"); + if (menu) { + // Add to list + foreach_(it, menu->items) { + if (it->submenu == L"AddToList") { + it->enabled = enabled; + break; + } + } + } +} + +void MenuList::UpdateSeason() { + // Group by + auto menu = menu_list_.FindMenu(L"SeasonGroup"); + if (menu) { + foreach_(it, menu->items) { + it->checked = false; + } + int item_index = DlgSeason.group_by; + if (item_index < static_cast(menu->items.size())) { + menu->items[item_index].checked = true; + } + } + + // Sort by + menu = menu_list_.FindMenu(L"SeasonSort"); + if (menu) { + foreach_(it, menu->items) { + it->checked = false; + if (it->action == L"Season_SortBy(2)") // Popularity + if (taiga::GetCurrentServiceId() != sync::kMyAnimeList) + it->visible = false; + } + int item_index = DlgSeason.sort_by; + if (item_index < static_cast(menu->items.size())) { + menu->items[item_index].checked = true; + } + } + + // View as + menu = menu_list_.FindMenu(L"SeasonView"); + if (menu) { + foreach_(it, menu->items) { + it->checked = false; + } + int item_index = DlgSeason.view_as; + if (item_index < static_cast(menu->items.size())) { + menu->items[item_index].checked = true; + } + } +} + +void MenuList::UpdateTools() { + auto menu = menu_list_.FindMenu(L"Tools"); + if (menu) { + foreach_(it, menu->items) { + // Tools > Enable anime recognition + if (it->action == L"ToggleRecognition()") + it->checked = Settings.GetBool(taiga::kApp_Option_EnableRecognition); + // Tools > Enable auto sharing + if (it->action == L"ToggleSharing()") + it->checked = Settings.GetBool(taiga::kApp_Option_EnableSharing); + // Tools > Enable auto synchronization + if (it->action == L"ToggleSynchronization()") + it->checked = Settings.GetBool(taiga::kApp_Option_EnableSync); + } + } +} + +void MenuList::UpdateTray() { + auto menu = menu_list_.FindMenu(L"Tray"); + if (menu) { + // Tray > Enable recognition + foreach_(it, menu->items) { + if (it->action == L"ToggleRecognition()") { + it->checked = Settings.GetBool(taiga::kApp_Option_EnableRecognition); + break; + } + } + } +} + +void MenuList::UpdateView() { + auto menu = menu_list_.FindMenu(L"View"); + if (menu) { + foreach_(it, menu->items) { + it->checked = false; + } + int item_index = DlgMain.navigation.GetCurrentPage(); + foreach_(it, menu->items) { + if (it->action == L"ViewContent(" + ToWstr(item_index) + L")") { + it->checked = true; + break; + } + } + menu->items.back().checked = !Settings.GetBool(taiga::kApp_Option_HideSidebar); + } +} + +void MenuList::UpdateAll(const anime::Item* anime_item) { + UpdateAnime(anime_item); + UpdateAnnounce(); + UpdateExternalLinks(); + UpdateFolders(); + UpdateTools(); + UpdateTray(); + UpdateView(); +} + +} // namespace ui \ No newline at end of file diff --git a/src/ui/menu.h b/src/ui/menu.h new file mode 100644 index 000000000..c5c4ff0c4 --- /dev/null +++ b/src/ui/menu.h @@ -0,0 +1,58 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_MENU_H +#define TAIGA_UI_MENU_H + +#include + +#include "win/win_menu.h" + +namespace anime { +class Item; +} + +namespace ui { + +class MenuList { +public: + void Load(); + + std::wstring Show(HWND hwnd, int x, int y, LPCWSTR lpName); + + void UpdateAll(const anime::Item* anime_item = nullptr); + void UpdateAnime(const anime::Item* anime_item); + void UpdateAnnounce(); + void UpdateExternalLinks(); + void UpdateFolders(); + void UpdateSearchList(bool enabled = false); + void UpdateSeasonList(bool enabled = false); + void UpdateSeason(); + void UpdateTools(); + void UpdateTray(); + void UpdateView(); + +private: + win::MenuList menu_list_; +}; + +extern MenuList Menus; + +} // namespace ui + +#endif // TAIGA_UI_MENU_H \ No newline at end of file diff --git a/src/ui/theme.cpp b/src/ui/theme.cpp new file mode 100644 index 000000000..5ebff4d6c --- /dev/null +++ b/src/ui/theme.cpp @@ -0,0 +1,180 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/gfx.h" +#include "base/string.h" +#include "base/xml.h" +#include "taiga/path.h" +#include "taiga/taiga.h" +#include "ui/theme.h" + +namespace ui { + +ThemeManager Theme; + +ThemeManager::ThemeManager() { + icons16_.Create(16, 16); // 16px + icons24_.Create(24, 24); // 24px +} + +bool ThemeManager::Load() { + xml_document document; + std::wstring path = taiga::GetPath(taiga::kPathThemeCurrent); + xml_parse_result parse_result = document.load_file(path.c_str()); + + if (parse_result.status != pugi::status_ok) { + std::wstring message = L"Could not read theme file:\n" + path; + MessageBox(nullptr, message.c_str(), TAIGA_APP_TITLE, MB_OK | MB_ICONERROR); + return false; + } + + xml_node node_icons16 = + document.child(L"theme").child(L"icons").child(L"set_16px"); + xml_node node_icons24 = + document.child(L"theme").child(L"icons").child(L"set_24px"); + xml_node node_image = + document.child(L"theme").child(L"list").child(L"background").child(L"image"); + xml_node node_progress = + document.child(L"theme").child(L"list").child(L"progress"); + + // Icons + std::vector icons16; + foreach_xmlnode_(node_icon, node_icons16, L"icon") + icons16.push_back(node_icon.attribute(L"name").value()); + std::vector icons24; + foreach_xmlnode_(node_icon, node_icons24, L"icon") + icons24.push_back(node_icon.attribute(L"name").value()); + + // List + #define READ_PROGRESS_DATA(x, name) \ + list_progress_[x].type = node_progress.child(name).attribute(L"type").value(); \ + list_progress_[x].value[0] = HexToARGB(node_progress.child(name).attribute(L"value_1").value()); \ + list_progress_[x].value[1] = HexToARGB(node_progress.child(name).attribute(L"value_2").value()); \ + list_progress_[x].value[2] = HexToARGB(node_progress.child(name).attribute(L"value_3").value()); + READ_PROGRESS_DATA(kListProgressAired, L"aired"); + READ_PROGRESS_DATA(kListProgressAvailable, L"available"); + READ_PROGRESS_DATA(kListProgressBackground, L"background"); + READ_PROGRESS_DATA(kListProgressBorder, L"border"); + READ_PROGRESS_DATA(kListProgressButton, L"button"); + READ_PROGRESS_DATA(kListProgressCompleted, L"completed"); + READ_PROGRESS_DATA(kListProgressDropped, L"dropped"); + READ_PROGRESS_DATA(kListProgressOnHold, L"onhold"); + READ_PROGRESS_DATA(kListProgressPlanToWatch, L"plantowatch"); + READ_PROGRESS_DATA(kListProgressSeparator, L"separator"); + READ_PROGRESS_DATA(kListProgressWatching, L"watching"); + #undef READ_PROGRESS_DATA + + // Load icons + icons16_.Remove(-1); + icons24_.Remove(-1); + path = GetPathOnly(taiga::GetPath(taiga::kPathThemeCurrent)); + HBITMAP bitmap_handle; + for (size_t i = 0; i < kIconCount16px && i < icons16.size(); i++) { + bitmap_handle = GdiPlus.LoadImage(path + L"16px\\" + + icons16.at(i) + L".png"); + icons16_.AddBitmap(bitmap_handle, CLR_NONE); + DeleteObject(bitmap_handle); + } + for (size_t i = 0; i < kIconCount24px && i < icons24.size(); i++) { + bitmap_handle = GdiPlus.LoadImage(path + L"24px\\" + + icons24.at(i) + L".png"); + icons24_.AddBitmap(bitmap_handle, CLR_NONE); + DeleteObject(bitmap_handle); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +void ThemeManager::CreateBrushes() { + if (brush_background_.Get()) + return; + + brush_background_.Set(CreateSolidBrush(GetSysColor(COLOR_WINDOW))); +} + +void ThemeManager::CreateFonts(HDC hdc) { + if (font_bold_.Get() && font_header_.Get()) + return; + + LOGFONT lFont = {0}; + lFont.lfCharSet = DEFAULT_CHARSET; + lFont.lfOutPrecision = OUT_STRING_PRECIS; + lFont.lfClipPrecision = CLIP_STROKE_PRECIS; + lFont.lfQuality = PROOF_QUALITY; + lFont.lfPitchAndFamily = VARIABLE_PITCH; + + // Bold font + lFont.lfHeight = -MulDiv(9, GetDeviceCaps(hdc, LOGPIXELSY), 72); + lFont.lfWeight = FW_BOLD; + lstrcpy(lFont.lfFaceName, L"Segoe UI"); + font_bold_.Set(::CreateFontIndirect(&lFont)); + + // Header font + lFont.lfHeight = -MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72); + lFont.lfWeight = FW_NORMAL; + lstrcpy(lFont.lfFaceName, L"Segoe UI"); + font_header_.Set(::CreateFontIndirect(&lFont)); +} + +HBRUSH ThemeManager::GetBackgroundBrush() const { + return brush_background_.Get(); +} + +HFONT ThemeManager::GetBoldFont() const { + return font_bold_.Get(); +} + +HFONT ThemeManager::GetHeaderFont() const { + return font_header_.Get(); +} + +win::ImageList& ThemeManager::GetImageList16() { + return icons16_; +} + +win::ImageList& ThemeManager::GetImageList24() { + return icons24_; +} + +void ThemeManager::DrawListProgress(HDC hdc, const LPRECT rect, + ListProgressType type) { + auto& item = list_progress_[type]; + + // Solid + if (item.type == L"solid") { + HBRUSH hbrSolid = CreateSolidBrush(item.value[0]); + FillRect(hdc, rect, hbrSolid); + DeleteObject(hbrSolid); + + // Gradient + } else if (item.type == L"gradient") { + GradientRect(hdc, rect, item.value[0], item.value[1], item.value[2] > 0); + + // Progress bar + } else if (item.type == L"progress") { + DrawProgressBar(hdc, rect, item.value[0], item.value[1], item.value[2]); + } +} + +COLORREF ThemeManager::GetListProgressColor(ListProgressType type) { + return list_progress_[type].value[0]; +} + +} // namespace ui \ No newline at end of file diff --git a/src/ui/theme.h b/src/ui/theme.h new file mode 100644 index 000000000..26abf7a68 --- /dev/null +++ b/src/ui/theme.h @@ -0,0 +1,142 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_THEME_H +#define TAIGA_UI_THEME_H + +#include +#include + +#include "win/ctrl/win_ctrl.h" +#include "win/win_gdi.h" + +namespace ui { + +enum Icons16px { + kIcon16_Green, + kIcon16_Blue, + kIcon16_Red, + kIcon16_Gray, + kIcon16_Play, + kIcon16_Search, + kIcon16_Folder, + kIcon16_AppBlue, + kIcon16_AppGray, + kIcon16_Refresh, + kIcon16_Download, + kIcon16_Settings, + kIcon16_Cross, + kIcon16_Plus, + kIcon16_Minus, + kIcon16_ArrowUp, + kIcon16_ArrowDown, + kIcon16_Funnel, + kIcon16_FunnelCross, + kIcon16_FunnelTick, + kIcon16_FunnelPlus, + kIcon16_FunnelPencil, + kIcon16_Calendar, + kIcon16_Category, + kIcon16_Sort, + kIcon16_Balloon, + kIcon16_Clock, + kIcon16_Home, + kIcon16_DocumentA, + kIcon16_Chart, + kIcon16_Feed, + kIcon16_Details, + kIconCount16px +}; + +enum Icons24px { + kIcon24_Sync, + kIcon24_Folders, + kIcon24_Tools, + kIcon24_Settings, + kIcon24_About, + kIcon24_Globe, + kIcon24_Library, + kIcon24_Application, + kIcon24_Recognition, + kIcon24_Sharing, + kIcon24_Feed, + kIconCount24px +}; + +enum ListProgressType { + kListProgressAired, + kListProgressAvailable, + kListProgressBackground, + kListProgressBorder, + kListProgressButton, + kListProgressCompleted, + kListProgressDropped, + kListProgressOnHold, + kListProgressPlanToWatch, + kListProgressSeparator, + kListProgressWatching +}; + +const COLORREF kColorDarkBlue = RGB(46, 81, 162); +const COLORREF kColorGray = RGB(230, 230, 230); +const COLORREF kColorLightBlue = RGB(225, 231, 245); +const COLORREF kColorLightGray = RGB(248, 248, 248); +const COLORREF kColorLightGreen = RGB(225, 245, 231); +const COLORREF kColorLightRed = RGB(245, 225, 231); +const COLORREF kColorMainInstruction = RGB(0x00, 0x33, 0x99); + +class ThemeManager { +public: + ThemeManager(); + ~ThemeManager() {} + + bool Load(); + + void CreateBrushes(); + void CreateFonts(HDC hdc); + HBRUSH GetBackgroundBrush() const; + HFONT GetBoldFont() const; + HFONT GetHeaderFont() const; + + win::ImageList& GetImageList16(); + win::ImageList& GetImageList24(); + + void DrawListProgress(HDC hdc, const LPRECT rect, ListProgressType type); + COLORREF GetListProgressColor(ListProgressType type); + +private: + win::Brush brush_background_; + + win::Font font_bold_; + win::Font font_header_; + + win::ImageList icons16_; + win::ImageList icons24_; + + struct Progress { + COLORREF value[3]; + std::wstring type; + }; + std::map list_progress_; +}; + +extern ThemeManager Theme; + +} // namespace ui + +#endif // TAIGA_UI_THEME_H \ No newline at end of file diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp new file mode 100644 index 000000000..1ae6e1c73 --- /dev/null +++ b/src/ui/ui.cpp @@ -0,0 +1,842 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "base/file.h" +#include "base/foreach.h" +#include "base/string.h" +#include "library/anime_db.h" +#include "library/anime_episode.h" +#include "library/anime_util.h" +#include "library/discover.h" +#include "library/history.h" +#include "sync/manager.h" +#include "sync/service.h" +#include "taiga/http.h" +#include "taiga/resource.h" +#include "taiga/script.h" +#include "taiga/settings.h" +#include "taiga/taiga.h" +#include "track/media.h" +#include "win/win_taskbar.h" +#include "ui/dlg/dlg_anime_info.h" +#include "ui/dlg/dlg_anime_list.h" +#include "ui/dlg/dlg_history.h" +#include "ui/dlg/dlg_input.h" +#include "ui/dlg/dlg_main.h" +#include "ui/dlg/dlg_search.h" +#include "ui/dlg/dlg_season.h" +#include "ui/dlg/dlg_settings.h" +#include "ui/dlg/dlg_stats.h" +#include "ui/dlg/dlg_torrent.h" +#include "ui/dlg/dlg_update.h" +#include "ui/dlg/dlg_update_new.h" +#include "ui/dialog.h" +#include "ui/menu.h" +#include "ui/theme.h" +#include "ui/ui.h" +#include "win/win_taskdialog.h" + +namespace ui { + +void ChangeStatusText(const string_t& status) { + DlgMain.ChangeStatus(status); +} + +void ClearStatusText() { + DlgMain.ChangeStatus(L""); +} + +void SetSharedCursor(LPCWSTR name) { + SetCursor(reinterpret_cast(LoadImage(nullptr, name, IMAGE_CURSOR, + 0, 0, LR_SHARED))); +} + +int StatusToIcon(int status) { + switch (status) { + case anime::kAiring: + return kIcon16_Green; + case anime::kFinishedAiring: + return kIcon16_Blue; + case anime::kNotYetAired: + return kIcon16_Red; + default: + return kIcon16_Gray; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void OnHttpError(const taiga::HttpClient& http_client, const string_t& error) { + switch (http_client.mode()) { + case taiga::kHttpSilent: + case taiga::kHttpServiceGetMetadataById: + case taiga::kHttpServiceGetMetadataByIdV2: + case taiga::kHttpServiceSearchTitle: + case taiga::kHttpGetLibraryEntryImage: + return; + case taiga::kHttpServiceAuthenticateUser: + case taiga::kHttpServiceGetLibraryEntries: + case taiga::kHttpServiceAddLibraryEntry: + case taiga::kHttpServiceDeleteLibraryEntry: + case taiga::kHttpServiceUpdateLibraryEntry: + ChangeStatusText(error); + DlgMain.EnableInput(true); + break; + case taiga::kHttpFeedCheck: + case taiga::kHttpFeedCheckAuto: + case taiga::kHttpFeedDownload: + case taiga::kHttpFeedDownloadAll: + ChangeStatusText(error); + DlgTorrent.EnableInput(); + break; + case taiga::kHttpTwitterRequest: + case taiga::kHttpTwitterAuth: + case taiga::kHttpTwitterPost: + ChangeStatusText(error); + break; + case taiga::kHttpTaigaUpdateCheck: + case taiga::kHttpTaigaUpdateDownload: + MessageBox(DlgUpdate.GetWindowHandle(), + error.c_str(), L"Update", MB_ICONERROR | MB_OK); + DlgUpdate.PostMessage(WM_CLOSE); + return; + } + + TaskbarList.SetProgressState(TBPF_NOPROGRESS); +} + +void OnHttpHeadersAvailable(const taiga::HttpClient& http_client) { + switch (http_client.mode()) { + case taiga::kHttpSilent: + return; + case taiga::kHttpTaigaUpdateCheck: + case taiga::kHttpTaigaUpdateDownload: + if (http_client.content_length() > 0) { + DlgUpdate.progressbar.SetMarquee(false); + DlgUpdate.progressbar.SetRange(0, http_client.content_length()); + } else { + DlgUpdate.progressbar.SetMarquee(true); + } + if (http_client.mode() == taiga::kHttpTaigaUpdateDownload) { + DlgUpdate.SetDlgItemText(IDC_STATIC_UPDATE_PROGRESS, + L"Downloading latest update..."); + } + break; + default: + TaskbarList.SetProgressState(http_client.content_length() > 0 ? + TBPF_NORMAL : TBPF_INDETERMINATE); + break; + } +} + +void OnHttpProgress(const taiga::HttpClient& http_client) { + std::wstring status; + + switch (http_client.mode()) { + case taiga::kHttpSilent: + case taiga::kHttpServiceGetMetadataById: + case taiga::kHttpServiceGetMetadataByIdV2: + case taiga::kHttpServiceSearchTitle: + case taiga::kHttpGetLibraryEntryImage: + return; + case taiga::kHttpServiceAuthenticateUser: + status = L"Reading account information..."; + break; + case taiga::kHttpServiceGetLibraryEntries: + status = L"Downloading anime list..."; + break; + case taiga::kHttpServiceAddLibraryEntry: + case taiga::kHttpServiceDeleteLibraryEntry: + case taiga::kHttpServiceUpdateLibraryEntry: + status = L"Updating list..."; + break; + case taiga::kHttpFeedCheck: + case taiga::kHttpFeedCheckAuto: + status = L"Checking new torrents..."; + break; + case taiga::kHttpFeedDownload: + case taiga::kHttpFeedDownloadAll: + status = L"Downloading torrent file..."; + break; + case taiga::kHttpTwitterRequest: + status = L"Connecting to Twitter..."; + break; + case taiga::kHttpTwitterAuth: + status = L"Authorizing Twitter..."; + break; + case taiga::kHttpTwitterPost: + status = L"Updating Twitter status..."; + break; + case taiga::kHttpTaigaUpdateCheck: + case taiga::kHttpTaigaUpdateDownload: + if (http_client.content_length() > 0) + DlgUpdate.progressbar.SetPosition(http_client.current_length()); + return; + } + + if (http_client.content_length() > 0) { + float current_length = static_cast(http_client.current_length()); + float content_length = static_cast(http_client.content_length()); + int percentage = static_cast((current_length / content_length) * 100); + status += L" (" + ToWstr(percentage) + L"%)"; + TaskbarList.SetProgressValue(static_cast(current_length), + static_cast(content_length)); + } else { + status += L" (" + ToSizeString(http_client.current_length()) + L")"; + } + + ChangeStatusText(status); +} + +void OnHttpReadComplete(const taiga::HttpClient& http_client) { + TaskbarList.SetProgressState(TBPF_NOPROGRESS); +} + +//////////////////////////////////////////////////////////////////////////////// + +void OnLibraryChange() { + ClearStatusText(); + + DlgAnimeList.RefreshList(); + DlgAnimeList.RefreshTabs(); + DlgHistory.RefreshList(); + DlgSearch.RefreshList(); + + DlgMain.EnableInput(true); +} + +void OnLibraryChangeFailure() { + DlgMain.EnableInput(true); +} + +void OnLibraryEntryAdd(int id) { + if (DlgAnime.GetCurrentId() == id) + DlgAnime.Refresh(); + + auto anime_item = AnimeDatabase.FindItem(id); + int status = anime_item->GetMyStatus(); + DlgAnimeList.RefreshList(status); + DlgAnimeList.RefreshTabs(status); + + if (DlgNowPlaying.GetCurrentId() == id) + DlgNowPlaying.Refresh(); + + DlgSearch.RefreshList(); +} + +void OnLibraryEntryChange(int id) { + if (DlgAnime.GetCurrentId() == id) + DlgAnime.Refresh(false, true, false, false); + + if (DlgAnimeList.IsWindow()) + DlgAnimeList.RefreshListItem(id); + + if (DlgNowPlaying.GetCurrentId() == id) + DlgNowPlaying.Refresh(false, true, false, false); + + if (DlgSeason.IsWindow()) + DlgSeason.RefreshList(true); +} + +void OnLibraryEntryDelete(int id) { + if (DlgAnime.GetCurrentId() == id) + DlgAnime.Destroy(); + + DlgAnimeList.RefreshList(); + DlgAnimeList.RefreshTabs(); + + DlgSearch.RefreshList(); + + if (DlgSeason.IsWindow()) + DlgSeason.RefreshList(true); + + if (CurrentEpisode.anime_id == id) + CurrentEpisode.Set(anime::ID_NOTINLIST); +} + +void OnLibraryEntryImageChange(int id) { + if (DlgAnime.GetCurrentId() == id) + DlgAnime.Refresh(true, false, false, false); + + if (DlgAnimeList.IsWindow()) + DlgAnimeList.RefreshListItem(id); + + if (DlgNowPlaying.GetCurrentId() == id) + DlgNowPlaying.Refresh(true, false, false, false); + + if (DlgSeason.IsWindow()) + DlgSeason.RefreshList(true); +} + +void OnLibrarySearchTitle(int id, const string_t& results) { + std::vector split_vector; + Split(results, L",", split_vector); + RemoveEmptyStrings(split_vector); + + std::vector ids; + foreach_(it, split_vector) { + int id = ToInt(*it); + ids.push_back(id); + OnLibraryEntryChange(id); + } + + if (id == anime::ID_UNKNOWN) + DlgSearch.ParseResults(ids); +} + +void OnLibraryEntryChangeFailure(int id, const string_t& reason) { + if (DlgAnime.GetCurrentId() == id) + DlgAnime.UpdateTitle(false); +} + +void OnLibraryUpdateFailure(int id, const string_t& reason) { + auto anime_item = AnimeDatabase.FindItem(id); + + std::wstring text; + if (anime_item) + text += L"Title: " + anime_item->GetTitle() + L"\n"; + if (!reason.empty()) + text += L"Reason: " + reason + L"\n"; + text += L"Click to try again."; + + Taiga.current_tip_type = taiga::kTipTypeUpdateFailed; + + Taskbar.Tip(L"", L"", 0); // clear previous tips + Taskbar.Tip(text.c_str(), L"Update failed", NIIF_ERROR); + + ChangeStatusText(L"Update failed: " + reason); +} + +//////////////////////////////////////////////////////////////////////////////// + +bool OnLibraryEntryEditDelete(int id) { + auto anime_item = AnimeDatabase.FindItem(id); + + win::TaskDialog dlg; + dlg.SetWindowTitle(L"Delete List Entry"); + dlg.SetMainIcon(TD_ICON_INFORMATION); + dlg.SetMainInstruction(L"Are you sure you want to delete this entry from " + L"your list?"); + dlg.SetContent(anime_item->GetTitle().c_str()); + dlg.AddButton(L"Yes", IDYES); + dlg.AddButton(L"No", IDNO); + dlg.Show(DlgMain.GetWindowHandle()); + + return dlg.GetSelectedButtonID() == IDYES; +} + +int OnLibraryEntryEditEpisode(int id) { + auto anime_item = AnimeDatabase.FindItem(id); + + InputDialog dlg; + dlg.SetNumbers(true, 0, anime_item->GetEpisodeCount(), + anime_item->GetMyLastWatchedEpisode()); + dlg.title = anime_item->GetTitle(); + dlg.info = L"Please enter episode number for this title:"; + dlg.text = ToWstr(anime_item->GetMyLastWatchedEpisode()); + dlg.Show(DlgMain.GetWindowHandle()); + + if (dlg.result == IDOK) + return ToInt(dlg.text); + + return -1; +} + +bool OnLibraryEntryEditTags(int id, std::wstring& tags) { + auto anime_item = AnimeDatabase.FindItem(id); + + InputDialog dlg; + dlg.title = anime_item->GetTitle(); + dlg.info = L"Please enter tags for this title, separated by a comma:"; + dlg.text = anime_item->GetMyTags(); + dlg.Show(DlgMain.GetWindowHandle()); + + if (dlg.result == IDOK) { + tags = dlg.text; + return true; + } + + return false; +} + +bool OnLibraryEntryEditTitles(int id, std::wstring& titles) { + auto anime_item = AnimeDatabase.FindItem(id); + + InputDialog dlg; + dlg.title = anime_item->GetTitle(); + dlg.info = L"Please enter alternative titles, separated by a semicolon:"; + dlg.text = Join(anime_item->GetUserSynonyms(), L"; "); + dlg.Show(DlgMain.GetWindowHandle()); + + if (dlg.result == IDOK) { + titles = dlg.text; + return true; + } + + return false; +} + +//////////////////////////////////////////////////////////////////////////////// + +void OnHistoryAddItem(const HistoryItem& history_item) { + OnHistoryChange(); + + if (history_item.mode == taiga::kHttpServiceAddLibraryEntry || + history_item.mode == taiga::kHttpServiceDeleteLibraryEntry || + history_item.status || + history_item.enable_rewatching) { + DlgAnimeList.RefreshList(); + DlgAnimeList.RefreshTabs(); + } else { + DlgAnimeList.RefreshListItem(history_item.anime_id); + } + + if (!Taiga.logged_in) { + auto anime_item = AnimeDatabase.FindItem(history_item.anime_id); + ChangeStatusText(L"\"" + anime_item->GetTitle() + + L"\" is queued for update."); + } +} + +void OnHistoryChange() { + DlgHistory.RefreshList(); + DlgMain.treeview.RefreshHistoryCounter(); + DlgNowPlaying.Refresh(false, false, false); + DlgAnimeList.RefreshList(); + DlgAnimeList.RefreshTabs(); +} + +int OnHistoryProcessConfirmationQueue(anime::Episode& episode) { + auto anime_item = AnimeDatabase.FindItem(episode.anime_id); + + win::TaskDialog dlg; + std::wstring title = L"Anime title: " + anime_item->GetTitle(); + dlg.SetWindowTitle(TAIGA_APP_TITLE); + dlg.SetMainIcon(TD_ICON_INFORMATION); + dlg.SetMainInstruction(L"Do you want to update your anime list?"); + dlg.SetContent(title.c_str()); + dlg.SetVerificationText(L"Don't ask again, update automatically"); + dlg.UseCommandLinks(true); + + int number = anime::GetEpisodeHigh(episode.number); + if (number == 0) + number = 1; + if (anime_item->GetEpisodeCount() == 1) + episode.number = L"1"; + + if (anime_item->GetMyStatus() != anime::kNotInList) { + if (anime_item->GetEpisodeCount() == number) { // Completed + dlg.AddButton(L"Update and move\n" + L"Update and set as completed", IDCANCEL); + } else if (anime_item->GetMyStatus() != anime::kWatching) { // Watching + dlg.AddButton(L"Update and move\n" + L"Update and set as watching", IDCANCEL); + } + } + std::wstring button = L"Update\n" + L"Update episode number from " + + ToWstr(anime_item->GetMyLastWatchedEpisode()) + + L" to " + ToWstr(number); + dlg.AddButton(button.c_str(), IDYES); + dlg.AddButton(L"Cancel\n" + L"Don't update anything", IDNO); + + dlg.Show(DlgMain.GetWindowHandle()); + if (dlg.GetVerificationCheck()) + Settings.Set(taiga::kSync_Update_AskToConfirm, false); + return dlg.GetSelectedButtonID(); +} + +//////////////////////////////////////////////////////////////////////////////// + +void OnAnimeEpisodeNotFound() { + win::TaskDialog dlg; + dlg.SetWindowTitle(L"Play Random Episode"); + dlg.SetMainIcon(TD_ICON_ERROR); + dlg.SetMainInstruction(L"Could not find any episode to play."); + dlg.Show(DlgMain.GetWindowHandle()); +} + +bool OnAnimeFolderNotFound() { + win::TaskDialog dlg; + dlg.SetWindowTitle(L"Folder Not Found"); + dlg.SetMainIcon(TD_ICON_INFORMATION); + dlg.SetMainInstruction(L"Taiga couldn't find the folder of this anime. " + L"Would you like to set it manually?"); + dlg.AddButton(L"Yes", IDYES); + dlg.AddButton(L"No", IDNO); + dlg.Show(DlgMain.GetWindowHandle()); + + return dlg.GetSelectedButtonID() == IDYES; +} + +void OnAnimeWatchingStart(const anime::Item& anime_item, + const anime::Episode& episode) { + DlgNowPlaying.SetCurrentId(anime_item.GetId()); + + int list_status = anime_item.GetMyStatus(); + if (anime_item.GetMyRewatching()) + list_status = anime::kWatching; + if (list_status != anime::kNotInList) { + DlgAnimeList.RefreshList(list_status); + DlgAnimeList.RefreshTabs(list_status); + } + int list_index = DlgAnimeList.GetListIndex(anime_item.GetId()); + if (list_index > -1) { + DlgAnimeList.listview.SetItemIcon(list_index, ui::kIcon16_Play); + DlgAnimeList.listview.RedrawItems(list_index, list_index, true); + DlgAnimeList.listview.EnsureVisible(list_index); + } + + DlgMain.UpdateTip(); + DlgMain.UpdateTitle(); + if (Settings.GetBool(taiga::kSync_Update_GoToNowPlaying)) + DlgMain.navigation.SetCurrentPage(kSidebarItemNowPlaying); + + if (Settings.GetBool(taiga::kSync_Notify_Recognized)) { + Taiga.current_tip_type = taiga::kTipTypeNowPlaying; + std::wstring tip_text = + ReplaceVariables(Settings[taiga::kSync_Notify_Format], episode); + Taskbar.Tip(L"", L"", 0); + Taskbar.Tip(tip_text.c_str(), L"Now Playing", NIIF_INFO); + } +} + +void OnAnimeWatchingEnd(const anime::Item& anime_item, + const anime::Episode& episode) { + DlgNowPlaying.SetCurrentId(anime::ID_UNKNOWN); + + DlgMain.UpdateTip(); + DlgMain.UpdateTitle(); + + int list_index = DlgAnimeList.GetListIndex(anime_item.GetId()); + if (list_index > -1) { + int icon_index = StatusToIcon(anime_item.GetAiringStatus()); + DlgAnimeList.listview.SetItemIcon(list_index, icon_index); + DlgAnimeList.listview.RedrawItems(list_index, list_index, true); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +bool OnRecognitionCancelConfirm() { + win::TaskDialog dlg; + std::wstring title = L"List Update"; + dlg.SetWindowTitle(title.c_str()); + dlg.SetMainIcon(TD_ICON_INFORMATION); + dlg.SetMainInstruction(L"Would you like to cancel this list update?"); + auto anime_item = AnimeDatabase.FindItem(CurrentEpisode.anime_id); + std::wstring content = anime_item->GetTitle(); + if (!CurrentEpisode.number.empty()) + content += L" #" + CurrentEpisode.number; + dlg.SetContent(content.c_str()); + dlg.AddButton(L"Yes", IDYES); + dlg.AddButton(L"No", IDNO); + dlg.Show(DlgMain.GetWindowHandle()); + + return dlg.GetSelectedButtonID() == IDYES; +} + +void OnRecognitionFail() { + if (!CurrentEpisode.title.empty()) { + MediaPlayers.set_title_changed(false); + DlgNowPlaying.SetCurrentId(anime::ID_NOTINLIST); + ChangeStatusText(L"Watching: " + CurrentEpisode.title + + PushString(L" #", CurrentEpisode.number) + + L" (Not recognized)"); + if (Settings.GetBool(taiga::kSync_Notify_NotRecognized)) { + std::wstring tip_text = + ReplaceVariables(Settings[taiga::kSync_Notify_Format], CurrentEpisode) + + L"\nClick here to view similar titles for this anime."; + Taiga.current_tip_type = taiga::kTipTypeNowPlaying; + Taskbar.Tip(L"", L"", 0); + Taskbar.Tip(tip_text.c_str(), L"Media is not in your list", NIIF_WARNING); + } + + } else { + if (Taiga.debug_mode) + ChangeStatusText(MediaPlayers.current_player() + L" is running."); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +bool OnSeasonRefreshRequired() { + win::TaskDialog dlg; + std::wstring title = L"Season - " + SeasonDatabase.name; + dlg.SetWindowTitle(title.c_str()); + dlg.SetMainIcon(TD_ICON_INFORMATION); + dlg.SetMainInstruction(L"Would you like to refresh this season's data?"); + auto service = taiga::GetCurrentService(); + std::wstring content = + L"Taiga will connect to " + service->name() + L" to retrieve missing " + L"information and images. Note that it may take about a minute until " + L"Taiga gets all the data."; + dlg.SetContent(content.c_str()); + dlg.AddButton(L"Yes", IDYES); + dlg.AddButton(L"No", IDNO); + dlg.Show(DlgMain.GetWindowHandle()); + + return dlg.GetSelectedButtonID() == IDYES; +} + +//////////////////////////////////////////////////////////////////////////////// + +void OnSettingsAccountEmpty() { + win::TaskDialog dlg(TAIGA_APP_TITLE, TD_ICON_INFORMATION); + dlg.SetMainInstruction(L"Would you like to set your account information?"); + dlg.SetContent(L"Anime search requires authentication, which means, you need " + L"to enter a valid username and password to search " + L"MyAnimeList."); + dlg.AddButton(L"Yes", IDYES); + dlg.AddButton(L"No", IDNO); + dlg.Show(DlgMain.GetWindowHandle()); + if (dlg.GetSelectedButtonID() == IDYES) + ShowDlgSettings(kSettingsSectionServices, kSettingsPageServicesMain); +} + +void OnSettingsChange() { + DlgAnimeList.RefreshList(); +} + +void OnSettingsRestoreDefaults() { + if (DlgSettings.IsWindow()) { + DestroyDialog(kDialogSettings); + ShowDialog(kDialogSettings); + } +} + +void OnSettingsRootFoldersEmpty() { + win::TaskDialog dlg(TAIGA_APP_TITLE, TD_ICON_INFORMATION); + dlg.SetMainInstruction(L"Would you like to set root anime folders first?"); + dlg.SetContent(L"You need to have at least one root folder set before " + L"scanning available episodes."); + dlg.AddButton(L"Yes", IDYES); + dlg.AddButton(L"No", IDNO); + dlg.Show(DlgMain.GetWindowHandle()); + if (dlg.GetSelectedButtonID() == IDYES) + ShowDlgSettings(kSettingsSectionLibrary, kSettingsPageLibraryFolders); +} + +void OnSettingsServiceChange() { + int current_page = DlgMain.navigation.GetCurrentPage(); + DlgMain.navigation.RefreshSearchText(current_page); +} + +bool OnSettingsServiceChangeConfirm(const string_t& current_service, + const string_t& new_service) { + win::TaskDialog dlg(TAIGA_APP_TITLE, TD_ICON_INFORMATION); + std::wstring instruction = + L"Are you sure you want to change the active service from " + + ServiceManager.service(current_service)->name() + L" to " + + ServiceManager.service(new_service)->name() + L"?"; + dlg.SetMainInstruction(instruction.c_str()); + dlg.SetContent(L"Note that:\n" + L"- Your list will not be moved from one service to another. " + L"Taiga can't do that.\n" + L"- Local settings associated with an anime will be lost or " + L"broken."); + dlg.AddButton(L"Yes", IDYES); + dlg.AddButton(L"No", IDNO); + dlg.Show(DlgSettings.GetWindowHandle()); + + return dlg.GetSelectedButtonID() == IDYES; +} + +void OnSettingsServiceChangeFailed() { + win::TaskDialog dlg(TAIGA_APP_TITLE, TD_ICON_ERROR); + dlg.SetMainInstruction(L"You cannot change the active service while there " + L"are queued items in your History."); + dlg.SetContent(L"Synchronize your list or clear the queue, and try again."); + dlg.AddButton(L"OK", IDOK); + dlg.Show(DlgMain.GetWindowHandle()); +} + +void OnSettingsThemeChange() { + Menus.UpdateAll(); + + DlgMain.rebar.RedrawWindow(); +} + +void OnSettingsUserChange() { + DlgMain.treeview.RefreshHistoryCounter(); + DlgMain.UpdateTitle(); + DlgAnimeList.RefreshList(anime::kWatching); + DlgAnimeList.RefreshTabs(anime::kWatching); + DlgHistory.RefreshList(); + DlgNowPlaying.Refresh(); + DlgSearch.RefreshList(); + DlgStats.Refresh(); +} + +//////////////////////////////////////////////////////////////////////////////// + +void OnFeedCheck(bool success) { + ChangeStatusText(success ? + L"There are new torrents available!" : L"No new torrents found."); + + DlgTorrent.RefreshList(); + DlgTorrent.EnableInput(); +} + +void OnFeedDownload(bool success) { + if (success) + DlgTorrent.RefreshList(); + + ChangeStatusText(L"Successfully downloaded all torrents."); + DlgTorrent.EnableInput(); +} + +bool OnFeedNotify(const Feed& feed) { + std::wstring tip_text; + std::wstring tip_title = L"New torrents available"; + std::wstring tip_format = L"%title%$if(%episode%, #%episode%)\n"; + + foreach_(it, feed.items) + if (it->state == kFeedItemSelected) + tip_text += L"\u00BB " + ReplaceVariables(tip_format, it->episode_data); + + if (tip_text.empty()) + return false; + + tip_text += L"Click to see all."; + tip_text = LimitText(tip_text, 255); + Taiga.current_tip_type = taiga::kTipTypeTorrent; + Taskbar.Tip(L"", L"", 0); + Taskbar.Tip(tip_text.c_str(), tip_title.c_str(), NIIF_INFO); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +void OnMircNotRunning(bool testing) { + std::wstring title = testing ? L"Test DDE connection" : L"Announce to mIRC"; + win::TaskDialog dlg(title.c_str(), TD_ICON_ERROR); + dlg.SetMainInstruction(L"mIRC is not running."); + dlg.AddButton(L"OK", IDOK); + dlg.Show(DlgMain.GetWindowHandle()); +} + +void OnMircDdeInitFail(bool testing) { + std::wstring title = testing ? L"Test DDE connection" : L"Announce to mIRC"; + win::TaskDialog dlg(title.c_str(), TD_ICON_ERROR); + dlg.SetMainInstruction(L"DDE initialization failed."); + dlg.AddButton(L"OK", IDOK); + dlg.Show(DlgMain.GetWindowHandle()); +} + +void OnMircDdeConnectionFail(bool testing) { + std::wstring title = testing ? L"Test DDE connection" : L"Announce to mIRC"; + win::TaskDialog dlg(title.c_str(), TD_ICON_ERROR); + dlg.SetMainInstruction(L"DDE connection failed."); + dlg.SetContent(L"Please enable DDE server from mIRC Options > Other > DDE."); + dlg.AddButton(L"OK", IDOK); + dlg.Show(DlgMain.GetWindowHandle()); +} + +void OnMircDdeConnectionSuccess(const std::wstring& channels, bool testing) { + std::wstring title = testing ? L"Test DDE connection" : L"Announce to mIRC"; + win::TaskDialog dlg(title.c_str(), TD_ICON_INFORMATION); + dlg.SetMainInstruction(L"Successfuly connected to DDE server!"); + std::wstring content = L"Current channels: " + channels; + dlg.SetContent(content.c_str()); + dlg.AddButton(L"OK", IDOK); + dlg.Show(DlgMain.GetWindowHandle()); +} + +//////////////////////////////////////////////////////////////////////////////// + +void OnTwitterTokenRequest(bool success) { + if (success) { + ClearStatusText(); + } else { + ChangeStatusText(L"Twitter token request failed."); + } +} + +bool OnTwitterTokenEntry(string_t& auth_pin) { + ClearStatusText(); + + InputDialog dlg; + dlg.title = L"Twitter Authorization"; + dlg.info = L"Please enter the PIN shown on the page after logging into " + L"Twitter:"; + dlg.Show(); + + if (dlg.result == IDOK && !dlg.text.empty()) { + auth_pin = dlg.text; + return true; + } + + return false; +} + +void OnTwitterAuth(bool success) { + ChangeStatusText(success ? + L"Taiga is now authorized to post to this Twitter account: " + + Settings[taiga::kShare_Twitter_Username] : + L"Twitter authorization failed."); + + DlgSettings.RefreshTwitterLink(); +} + +void OnTwitterPost(bool success, const string_t& error) { + ChangeStatusText(success ? + L"Twitter status updated." : + L"Twitter status update failed. (" + error + L")"); +} + +//////////////////////////////////////////////////////////////////////////////// + +void OnLogin() { + ChangeStatusText(L"Logged in as " + taiga::GetCurrentUsername()); + + Menus.UpdateAll(); + + DlgMain.UpdateTip(); + DlgMain.UpdateTitle(); + DlgMain.EnableInput(true); +} + +void OnLogout() { + DlgMain.EnableInput(true); +} + +//////////////////////////////////////////////////////////////////////////////// + +void OnUpdateAvailable() { + DlgUpdateNew.Create(IDD_UPDATE_NEW, DlgUpdate.GetWindowHandle(), true); +} + +void OnUpdateNotAvailable() { + if (DlgMain.IsWindow()) { + win::TaskDialog dlg(L"Update", TD_ICON_INFORMATION); + std::wstring footer = L"Current version: " + std::wstring(Taiga.version); + dlg.SetFooter(footer.c_str()); + dlg.SetMainInstruction(L"No updates available. Taiga is up to date!"); + dlg.AddButton(L"OK", IDOK); + dlg.Show(DlgUpdate.GetWindowHandle()); + } +} + +void OnUpdateFinished() { + DlgUpdate.PostMessage(WM_CLOSE); +} + +} // namespace ui \ No newline at end of file diff --git a/src/ui/ui.h b/src/ui/ui.h new file mode 100644 index 000000000..d7e227473 --- /dev/null +++ b/src/ui/ui.h @@ -0,0 +1,108 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_UI_UI_H +#define TAIGA_UI_UI_H + +#include "base/types.h" + +namespace anime { +class Episode; +class Item; +}; +class HistoryItem; +namespace taiga { +class HttpClient; +} +class Feed; + +namespace ui { + +void ChangeStatusText(const string_t& status); +void ClearStatusText(); +void SetSharedCursor(LPCWSTR name); +int StatusToIcon(int status); + +void OnHttpError(const taiga::HttpClient& http_client, const string_t& error); +void OnHttpHeadersAvailable(const taiga::HttpClient& http_client); +void OnHttpProgress(const taiga::HttpClient& http_client); +void OnHttpReadComplete(const taiga::HttpClient& http_client); + +void OnLibraryChange(); +void OnLibraryChangeFailure(); +void OnLibraryEntryAdd(int id); +void OnLibraryEntryChange(int id); +void OnLibraryEntryDelete(int id); +void OnLibraryEntryImageChange(int id); +void OnLibrarySearchTitle(int id, const string_t& results); +void OnLibraryEntryChangeFailure(int id, const string_t& reason); +void OnLibraryUpdateFailure(int id, const string_t& reason); + +bool OnLibraryEntryEditDelete(int id); +int OnLibraryEntryEditEpisode(int id); +bool OnLibraryEntryEditTags(int id, std::wstring& tags); +bool OnLibraryEntryEditTitles(int id, std::wstring& titles); + +void OnHistoryAddItem(const HistoryItem& history_item); +void OnHistoryChange(); +int OnHistoryProcessConfirmationQueue(anime::Episode& episode); + +void OnAnimeEpisodeNotFound(); +bool OnAnimeFolderNotFound(); +void OnAnimeWatchingStart(const anime::Item& anime_item, const anime::Episode& episode); +void OnAnimeWatchingEnd(const anime::Item& anime_item, const anime::Episode& episode); + +bool OnRecognitionCancelConfirm(); +void OnRecognitionFail(); + +bool OnSeasonRefreshRequired(); + +void OnSettingsAccountEmpty(); +void OnSettingsChange(); +void OnSettingsRestoreDefaults(); +void OnSettingsRootFoldersEmpty(); +void OnSettingsServiceChange(); +bool OnSettingsServiceChangeConfirm(const string_t& current_service, const string_t& new_service); +void OnSettingsServiceChangeFailed(); +void OnSettingsThemeChange(); +void OnSettingsUserChange(); + +void OnFeedCheck(bool success); +void OnFeedDownload(bool success); +bool OnFeedNotify(const Feed& feed); + +void OnMircNotRunning(bool testing = false); +void OnMircDdeInitFail(bool testing = false); +void OnMircDdeConnectionFail(bool testing = false); +void OnMircDdeConnectionSuccess(const std::wstring& channels, bool testing = false); + +void OnTwitterTokenRequest(bool success); +bool OnTwitterTokenEntry(string_t& auth_pin); +void OnTwitterAuth(bool success); +void OnTwitterPost(bool success, const string_t& error); + +void OnLogin(); +void OnLogout(); + +void OnUpdateAvailable(); +void OnUpdateNotAvailable(); +void OnUpdateFinished(); + +} // namespace ui + +#endif // TAIGA_UI_UI_H \ No newline at end of file diff --git a/src/win/ctrl/win_ctrl.h b/src/win/ctrl/win_ctrl.h new file mode 100644 index 000000000..c665735f0 --- /dev/null +++ b/src/win/ctrl/win_ctrl.h @@ -0,0 +1,408 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_WIN_CTRL_H +#define TAIGA_WIN_CTRL_H + +#include +#include +#include + +#include "win/win_main.h" +#include "win/win_window.h" + +namespace win { + +//////////////////////////////////////////////////////////////////////////////// +// ComboBox + +class ComboBox : public Window { +public: + ComboBox() {} + ComboBox(HWND hwnd); + virtual ~ComboBox() {} + + int AddItem(LPCWSTR text, LPARAM data); + int AddString(LPCWSTR text); + int DeleteString(int index); + int FindItemData(LPARAM data); + int GetCount(); + LRESULT GetItemData(int index); + int GetCurSel(); + void ResetContent(); + BOOL SetCueBannerText(LPCWSTR text); + int SetCurSel(int index); + BOOL SetEditSel(int start, int end); + int SetItemData(int index, LPARAM data); + +protected: + virtual void PreCreate(CREATESTRUCT &cs); + virtual void OnCreate(HWND hwnd, LPCREATESTRUCT create_struct); +}; + +//////////////////////////////////////////////////////////////////////////////// +// Edit + +class Edit : public Window { +public: + Edit() {} + Edit(HWND hwnd); + virtual ~Edit() {} + + void GetRect(LPRECT rect); + void LimitText(int limit); + BOOL SetCueBannerText(LPCWSTR text, BOOL draw_focused = FALSE); + void SetMargins(int left = -1, int right = -1); + void SetMultiLine(BOOL enabled); + void SetPasswordChar(UINT ch); + void SetReadOnly(BOOL read_only); + void SetRect(LPRECT rect); + void SetSel(int start, int end); + BOOL ShowBalloonTip(LPCWSTR text, LPCWSTR title, INT icon); + +protected: + virtual void PreCreate(CREATESTRUCT &cs); + virtual void OnCreate(HWND hwnd, LPCREATESTRUCT create_struct); +}; + +//////////////////////////////////////////////////////////////////////////////// +// Image list + +class ImageList { +public: + ImageList(); + ~ImageList(); + + void operator=(const HIMAGELIST image_list); + + int AddBitmap(HBITMAP bitmap, COLORREF mask); + BOOL BeginDrag(int track, int hotspot_x, int hotspot_y); + BOOL Create(int cx, int cy); + VOID Destroy(); + BOOL DragEnter(HWND hwnd_lock, int x, int y); + BOOL DragLeave(HWND hwnd_lock); + BOOL DragMove(int x, int y); + BOOL Draw(int index, HDC hdc, int x, int y); + VOID EndDrag(); + HIMAGELIST GetHandle(); + HICON GetIcon(int index); + BOOL Remove(int index); + VOID SetHandle(HIMAGELIST image_list); + +private: + HIMAGELIST image_list_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// List view + +class ListView : public Window { +public: + ListView(); + ListView(HWND hwnd); + virtual ~ListView() {} + + HIMAGELIST CreateDragImage(int item, LPPOINT pt_up_left); + BOOL DeleteAllItems(); + BOOL DeleteItem(int item); + int EnableGroupView(bool enable); + BOOL EnsureVisible(int item); + BOOL GetCheckState(UINT index); + HWND GetHeader(); + int GetItemCount(); + LPARAM GetItemParam(int i); + void GetItemText(int item, int subitem, LPWSTR output, int max_length = MAX_PATH); + void GetItemText(int item, int subitem, std::wstring& output, int max_length = MAX_PATH); + INT GetNextItem(int start, UINT flags); + INT GetNextItemIndex(int item, int group, LPARAM flags); + UINT GetSelectedCount(); + INT GetSelectionMark(); + int GetSortColumn(); + int GetSortOrder(); + int GetSortType(); + BOOL GetSubItemRect(int item, int subitem, LPRECT rect); + DWORD GetView(); + int HitTest(bool return_subitem = false); + int HitTestEx(LPLVHITTESTINFO lplvhi); + int InsertColumn(int index, int width, int width_min, int align, LPCWSTR text); + int InsertGroup(int index, LPCWSTR text, bool collapsable = false, bool collapsed = false); + int InsertItem(const LVITEM& lvi); + int InsertItem(int index, int nGroup, int icon, UINT column_count, PUINT columns, LPCWSTR text, LPARAM lParam); + BOOL IsGroupViewEnabled(); + BOOL RedrawItems(int first, int last, bool repaint); + void RemoveAllGroups(); + BOOL SetBkImage(HBITMAP bitmap, ULONG flags = LVBKIF_TYPE_WATERMARK, int offset_x = 100, int offset_y = 100); + void SetCheckState(int index, BOOL check); + BOOL SetColumnWidth(int column, int cx); + void SetExtendedStyle(DWORD ex_style); + int SetGroupText(int index, LPCWSTR text); + DWORD SetHoverTime(DWORD hover_time); + void SetImageList(HIMAGELIST image_list, int type = LVSIL_SMALL); + BOOL SetItem(int index, int subitem, LPCWSTR text); + BOOL SetItemIcon(int index, int icon); + void SetSelectedItem(int index); + BOOL SetTileViewInfo(PLVTILEVIEWINFO tvi); + BOOL SetTileViewInfo(int line_count, DWORD flags, RECT* rc_label_margin = nullptr, SIZE* size_tile = nullptr); + int SetView(DWORD view); + void Sort(int sort_column, int sort_order, int type, PFNLVCOMPARE compare); + +protected: + virtual void PreCreate(CREATESTRUCT &cs); + virtual void OnCreate(HWND hwnd, LPCREATESTRUCT create_struct); + +private: + int sort_column_, sort_order_, sort_type_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Progress bar + +class ProgressBar : public Window { +public: + ProgressBar() {} + ProgressBar(HWND hwnd); + virtual ~ProgressBar() {} + + UINT GetPosition(); + void SetMarquee(bool enabled); + UINT SetPosition(UINT position); + DWORD SetRange(UINT min, UINT max); + UINT SetState(UINT state); + +protected: + virtual void PreCreate(CREATESTRUCT &cs); + virtual void OnCreate(HWND hwnd, LPCREATESTRUCT create_struct); +}; + +//////////////////////////////////////////////////////////////////////////////// +// Rebar + +class Rebar : public Window { +public: + Rebar() {} + Rebar(HWND hwnd); + virtual ~Rebar() {} + + UINT GetBarHeight(); + BOOL InsertBand(LPREBARBANDINFO bar_info); + BOOL InsertBand( + HWND hwnd_child, + UINT cx, UINT cx_header, UINT cx_ideal, + UINT cx_min_child, + UINT cy_child, UINT cy_integral, + UINT cy_max_child, UINT cy_min_child, + UINT mask, UINT style); + +protected: + virtual void PreCreate(CREATESTRUCT &cs); + virtual void OnCreate(HWND hwnd, LPCREATESTRUCT create_struct); +}; + +//////////////////////////////////////////////////////////////////////////////// +// Rich edit + +class RichEdit : public Window { +public: + RichEdit(); + RichEdit(HWND hwnd); + virtual ~RichEdit(); + + void GetSel(CHARRANGE* cr); + std::wstring GetTextRange(CHARRANGE* cr); + void HideSelection(BOOL hide); + BOOL SetCharFormat(DWORD format, CHARFORMAT* cf); + DWORD SetEventMask(DWORD flags); + void SetSel(int start, int end); + void SetSel(CHARRANGE* cr); + UINT SetTextEx(const std::string& text); + +protected: + virtual void PreCreate(CREATESTRUCT &cs); + virtual void OnCreate(HWND hwnd, LPCREATESTRUCT create_struct); + +private: + HMODULE module_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Spin + +class Spin : public Window { +public: + Spin() {} + Spin(HWND hwnd); + virtual ~Spin() {} + + bool GetPos32(int& value); + HWND SetBuddy(HWND hwnd); + int SetPos32(int position); + void SetRange32(int lower_limit, int upper_limit); +}; + +//////////////////////////////////////////////////////////////////////////////// +// Status bar + +class StatusBar : public Window { +public: + StatusBar() {} + StatusBar(HWND hwnd); + virtual ~StatusBar() {} + + void GetRect(int part, LPRECT rect); + int InsertPart(int image, int style, int autosize, int width, LPCWSTR text, LPCWSTR tooltip); + void SetImageList(HIMAGELIST image_list); + void SetPartText(int part, LPCWSTR text); + void SetPartTipText(int part, LPCWSTR tip_text); + void SetPartWidth(int part, int width); + +protected: + virtual void PreCreate(CREATESTRUCT &cs); + virtual void OnCreate(HWND hwnd, LPCREATESTRUCT create_struct); + +private: + HIMAGELIST image_list_; + std::vector widths_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// SysLink + +class SysLink : public Window { +public: + SysLink() {} + SysLink(HWND hwnd); + virtual ~SysLink() {} + + void SetItemState(int item, UINT states); +}; + +//////////////////////////////////////////////////////////////////////////////// +// Tab + +class Tab : public Window { +public: + Tab() {} + Tab(HWND hwnd); + virtual ~Tab() {} + + void AdjustRect(HWND hwnd, BOOL larger, LPRECT rect); + int DeleteAllItems(); + int DeleteItem(int index); + int InsertItem(int index, LPCWSTR text, LPARAM param); + int GetCurrentlySelected(); + int GetItemCount(); + LPARAM GetItemParam(int index); + int HitTest(); + int SetCurrentlySelected(int item); + int SetItemText(int item, LPCWSTR text); + +protected: + virtual void PreCreate(CREATESTRUCT &cs); + virtual void OnCreate(HWND hwnd, LPCREATESTRUCT create_struct); +}; + +//////////////////////////////////////////////////////////////////////////////// +// Toolbar + +class Toolbar : public Window { +public: + Toolbar() {} + Toolbar(HWND hwnd); + virtual ~Toolbar() {} + + BOOL EnableButton(int command_id, bool enabled); + int GetHeight(); + BOOL GetButton(int index, TBBUTTON& tbb); + int GetButtonCount(); + DWORD GetButtonSize(); + DWORD GetButtonStyle(int index); + LPCWSTR GetButtonTooltip(int index); + DWORD GetPadding(); + int HitTest(POINT& point); + BOOL InsertButton(int index, int bitmap, int command_id, + BYTE state, BYTE style, DWORD_PTR data, + LPCWSTR text, LPCWSTR tooltip); + BOOL PressButton(int command_id, BOOL press); + BOOL SetButtonImage(int index, int image); + BOOL SetButtonText(int index, LPCWSTR text); + BOOL SetButtonTooltip(int index, LPCWSTR tooltip); + void SetImageList(HIMAGELIST image_list, int dx, int dy); + +protected: + virtual void PreCreate(CREATESTRUCT &cs); + virtual void OnCreate(HWND hwnd, LPCREATESTRUCT create_struct); + +private: + std::vector tooltip_text_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Tooltip + +class Tooltip : public Window { +public: + Tooltip() {} + Tooltip(HWND hwnd); + virtual ~Tooltip() {} + + BOOL AddTip(UINT id, LPCWSTR text, LPCWSTR title, LPRECT rect, bool window_id); + BOOL DeleteTip(UINT id); + void SetDelayTime(long autopop, long initial, long reshow); + void SetMaxWidth(long width); + void UpdateText(UINT id, LPCWSTR text); + void UpdateTitle(LPCWSTR title); + +protected: + virtual void PreCreate(CREATESTRUCT &cs); + virtual void OnCreate(HWND hwnd, LPCREATESTRUCT create_struct); +}; + +//////////////////////////////////////////////////////////////////////////////// +// Tree view + +class TreeView : public Window { +public: + TreeView() {} + TreeView(HWND hwnd); + virtual ~TreeView() {} + + BOOL DeleteAllItems(); + BOOL DeleteItem(HTREEITEM item); + BOOL Expand(HTREEITEM item, bool expand = true); + UINT GetCheckState(HTREEITEM item); + UINT GetCount(); + BOOL GetItem(LPTVITEM item); + LPARAM GetItemData(HTREEITEM item); + HTREEITEM GetSelection(); + HTREEITEM HitTest(LPTVHITTESTINFO hti, bool get_cursor_pos = false); + HTREEITEM InsertItem(LPCWSTR text, int image, LPARAM param, + HTREEITEM parent, HTREEITEM insert_after = TVI_LAST); + BOOL SelectItem(HTREEITEM item); + UINT SetCheckState(HTREEITEM item, BOOL check); + HIMAGELIST SetImageList(HIMAGELIST image_list, INT image = TVSIL_NORMAL); + BOOL SetItem(HTREEITEM item, LPCWSTR text); + int SetItemHeight(SHORT cyItem); + +protected: + virtual void PreCreate(CREATESTRUCT &cs); + virtual void OnCreate(HWND hwnd, LPCREATESTRUCT create_struct); +}; + +} // namespace win + +#endif // TAIGA_WIN_CTRL_H \ No newline at end of file diff --git a/src/win/ctrl/win_ctrl_combobox.cpp b/src/win/ctrl/win_ctrl_combobox.cpp new file mode 100644 index 000000000..6bcdfdefe --- /dev/null +++ b/src/win/ctrl/win_ctrl_combobox.cpp @@ -0,0 +1,93 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_ctrl.h" +#include + +namespace win { + +ComboBox::ComboBox(HWND hwnd) { + SetWindowHandle(hwnd); +} + +void ComboBox::PreCreate(CREATESTRUCT &cs) { + cs.dwExStyle = WS_EX_CLIENTEDGE; + cs.lpszClass = WC_COMBOBOX; + cs.style = CBS_DROPDOWN | CBS_HASSTRINGS | WS_CHILD | WS_TABSTOP | WS_VISIBLE; +} + +void ComboBox::OnCreate(HWND hwnd, LPCREATESTRUCT create_struct) { + Window::OnCreate(hwnd, create_struct); +} + +//////////////////////////////////////////////////////////////////////////////// + +int ComboBox::AddItem(LPCWSTR text, LPARAM data) { + int index = ComboBox_AddString(window_, text); + return ComboBox_SetItemData(window_, index, data); +} + +int ComboBox::AddString(LPCWSTR text) { + return ComboBox_AddString(window_, text); +} + +int ComboBox::DeleteString(int index) { + return ComboBox_DeleteString(window_, index); +} + +int ComboBox::FindItemData(LPARAM data) { + for (int i = 0; i < GetCount(); i++) + if (data == GetItemData(i)) + return i; + + return CB_ERR; +} + +int ComboBox::GetCount() { + return ComboBox_GetCount(window_); +} + +int ComboBox::GetCurSel() { + return ComboBox_GetCurSel(window_); +} + +LRESULT ComboBox::GetItemData(int index) { + return ComboBox_GetItemData(window_, index); +} + +void ComboBox::ResetContent() { + ComboBox_ResetContent(window_); +} + +BOOL ComboBox::SetCueBannerText(LPCWSTR text) { + return ComboBox_SetCueBannerText(window_, text); +} + +int ComboBox::SetCurSel(int index) { + return ComboBox_SetCurSel(window_, index); +} + +BOOL ComboBox::SetEditSel(int start, int end) { + return ::SendMessage(window_, CB_SETEDITSEL, start, end); +} + +int ComboBox::SetItemData(int index, LPARAM data) { + return ComboBox_SetItemData(window_, index, data); +} + +} // namespace win \ No newline at end of file diff --git a/src/win/ctrl/win_ctrl_edit.cpp b/src/win/ctrl/win_ctrl_edit.cpp new file mode 100644 index 000000000..bc6cc7f9b --- /dev/null +++ b/src/win/ctrl/win_ctrl_edit.cpp @@ -0,0 +1,99 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_ctrl.h" + +namespace win { + +Edit::Edit(HWND hwnd) { + SetWindowHandle(hwnd); +} + +void Edit::PreCreate(CREATESTRUCT &cs) { + cs.dwExStyle = WS_EX_CLIENTEDGE; + cs.lpszClass = L"EDIT"; + cs.style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_LEFT | ES_AUTOHSCROLL; +} + +void Edit::OnCreate(HWND hwnd, LPCREATESTRUCT create_struct) { + Window::OnCreate(hwnd, create_struct); +} + +//////////////////////////////////////////////////////////////////////////////// + +void Edit::GetRect(LPRECT rect) { + SendMessage(EM_GETRECT, 0, reinterpret_cast(rect)); +} + +void Edit::LimitText(int limit) { + SendMessage(EM_SETLIMITTEXT, limit, 0); +} + +BOOL Edit::SetCueBannerText(LPCWSTR text, BOOL draw_focused) { + return SendMessage(EM_SETCUEBANNER, draw_focused, + reinterpret_cast(text)); +} + +void Edit::SetMargins(int left, int right) { + DWORD flags = 0; + + if (left > -1) + flags |= EC_LEFTMARGIN; + if (right > -1) + flags |= EC_RIGHTMARGIN; + + SendMessage(EM_SETMARGINS, flags, MAKELPARAM(left, right)); +} + +void Edit::SetMultiLine(BOOL enabled) { + // TODO: We have to re-create the control, this does not work. + if (enabled) { + SetStyle(ES_MULTILINE | ES_AUTOVSCROLL, ES_AUTOHSCROLL); + } else { + SetStyle(ES_AUTOHSCROLL, ES_MULTILINE | ES_AUTOVSCROLL); + } +} + +void Edit::SetPasswordChar(UINT ch) { + SendMessage(EM_SETPASSWORDCHAR, ch, 0); +} + +void Edit::SetReadOnly(BOOL read_only) { + SendMessage(EM_SETREADONLY, read_only, 0); +} + +void Edit::SetRect(LPRECT rect) { + SendMessage(EM_SETRECT, 0, reinterpret_cast(rect)); +} + +void Edit::SetSel(int start, int end) { + SendMessage(EM_SETSEL, start, end); + SendMessage(EM_SCROLLCARET, 0, 0); +} + +BOOL Edit::ShowBalloonTip(LPCWSTR text, LPCWSTR title, INT icon) { + EDITBALLOONTIP ebt; + ebt.cbStruct = sizeof(EDITBALLOONTIP); + ebt.pszText = text; + ebt.pszTitle = title; + ebt.ttiIcon = icon; + + return SendMessage(EM_SHOWBALLOONTIP, 0, reinterpret_cast(&ebt)); +} + +} // namespace win \ No newline at end of file diff --git a/src/win/ctrl/win_ctrl_imagelist.cpp b/src/win/ctrl/win_ctrl_imagelist.cpp new file mode 100644 index 000000000..534d86a12 --- /dev/null +++ b/src/win/ctrl/win_ctrl_imagelist.cpp @@ -0,0 +1,102 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_ctrl.h" + +namespace win { + +ImageList::ImageList() + : image_list_(nullptr) { +} + +ImageList::~ImageList() { + Destroy(); +} + +void ImageList::operator=(const HIMAGELIST image_list) { + SetHandle(image_list); +} + +BOOL ImageList::Create(int cx, int cy) { + Destroy(); + + image_list_ = ::ImageList_Create(cx, cy, ILC_COLOR32 | ILC_MASK, 0, 0); + + return image_list_ != nullptr; +} + +VOID ImageList::Destroy() { + if (image_list_) { + ::ImageList_Destroy(image_list_); + image_list_ = nullptr; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +int ImageList::AddBitmap(HBITMAP bitmap, COLORREF mask) { + if (mask != CLR_NONE) { + return ::ImageList_AddMasked(image_list_, bitmap, mask); + } else { + return ::ImageList_Add(image_list_, bitmap, nullptr); + } +} + +BOOL ImageList::BeginDrag(int track, int hotspot_x, int hotspot_y) { + return ::ImageList_BeginDrag(image_list_, track, hotspot_x, hotspot_y); +} + +BOOL ImageList::DragEnter(HWND hwnd_lock, int x, int y) { + return ::ImageList_DragEnter(hwnd_lock, x, y); +} + +BOOL ImageList::DragLeave(HWND hwnd_lock) { + return ::ImageList_DragLeave(hwnd_lock); +} + +BOOL ImageList::DragMove(int x, int y) { + return ::ImageList_DragMove(x, y); +} + +BOOL ImageList::Draw(int index, HDC hdc, int x, int y) { + return ::ImageList_Draw(image_list_, index, hdc, x, y, ILD_NORMAL); +} + +VOID ImageList::EndDrag() { + ImageList_EndDrag(); +} + +HIMAGELIST ImageList::GetHandle() { + return image_list_; +} + +HICON ImageList::GetIcon(int index) { + return ::ImageList_GetIcon(image_list_, index, ILD_NORMAL); +} + +BOOL ImageList::Remove(int index) { + return ::ImageList_Remove(image_list_, index); +} + +VOID ImageList::SetHandle(HIMAGELIST image_list) { + Destroy(); + + image_list_ = image_list; +} + +} // namespace win \ No newline at end of file diff --git a/src/win/ctrl/win_ctrl_listview.cpp b/src/win/ctrl/win_ctrl_listview.cpp new file mode 100644 index 000000000..bb8236b80 --- /dev/null +++ b/src/win/ctrl/win_ctrl_listview.cpp @@ -0,0 +1,377 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_ctrl.h" + +namespace win { + +ListView::ListView() + : sort_column_(-1), + sort_order_(1), + sort_type_(0) { +} + +ListView::ListView(HWND hwnd) + : sort_column_(-1), + sort_order_(1), + sort_type_(0) { + SetWindowHandle(hwnd); +} + +void ListView::PreCreate(CREATESTRUCT &cs) { + cs.dwExStyle = WS_EX_CLIENTEDGE; + cs.lpszClass = WC_LISTVIEW; + cs.style = WS_CHILD | WS_TABSTOP | WS_VISIBLE | + LVS_ALIGNLEFT | LVS_AUTOARRANGE | LVS_REPORT | + LVS_SHAREIMAGELISTS | LVS_SINGLESEL; +} + +void ListView::OnCreate(HWND hwnd, LPCREATESTRUCT create_struct) { + ListView_SetExtendedListViewStyle(hwnd, + LVS_EX_AUTOSIZECOLUMNS | LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | + LVS_EX_INFOTIP | LVS_EX_LABELTIP); + Window::OnCreate(hwnd, create_struct); +} + +//////////////////////////////////////////////////////////////////////////////// + +int ListView::InsertColumn(int index, int width, int width_min, int align, + LPCWSTR text) { + LVCOLUMN lvc = {0}; + lvc.cx = width; + lvc.cxMin = width_min; + lvc.fmt = align; + lvc.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH | + (width_min ? LVCF_MINWIDTH : 0); + lvc.pszText = const_cast(text); + + if (GetVersion() < kVersionVista) + lvc.cx = lvc.cxMin; + + return ListView_InsertColumn(window_, index, &lvc); +} + +//////////////////////////////////////////////////////////////////////////////// + +int ListView::EnableGroupView(bool enable) { + return ListView_EnableGroupView(window_, enable); +} + +int ListView::InsertGroup(int index, LPCWSTR text, + bool collapsable, bool collapsed) { + LVGROUP lvg = {0}; + lvg.cbSize = sizeof(LVGROUP); + lvg.iGroupId = index; + lvg.mask = LVGF_HEADER | LVGF_GROUPID; + lvg.pszHeader = const_cast(text); + + if (collapsable && GetVersion() >= kVersionVista) { + lvg.mask |= LVGF_STATE; + lvg.state = LVGS_COLLAPSIBLE; + if (collapsed) + lvg.state |= LVGS_COLLAPSED; + } + + return ListView_InsertGroup(window_, index, &lvg); +} + +BOOL ListView::IsGroupViewEnabled() { + return ListView_IsGroupViewEnabled(window_); +} + +int ListView::SetGroupText(int index, LPCWSTR text) { + LVGROUP lvg = {0}; + lvg.cbSize = sizeof(LVGROUP); + lvg.mask = LVGF_HEADER; + lvg.pszHeader = const_cast(text); + + return ListView_SetGroupInfo(window_, index, &lvg); +} + +//////////////////////////////////////////////////////////////////////////////// + +HIMAGELIST ListView::CreateDragImage(int item, LPPOINT pt_up_left) { + return ListView_CreateDragImage(window_, item, pt_up_left); +} + +BOOL ListView::DeleteAllItems() { + return ListView_DeleteAllItems(window_); +} + +BOOL ListView::DeleteItem(int item) { + return ListView_DeleteItem(window_, item); +} + +BOOL ListView::EnsureVisible(int item) { + return ListView_EnsureVisible(window_, item, false); +} + +BOOL ListView::GetCheckState(UINT index) { + return ListView_GetCheckState(window_, index); +} + +HWND ListView::GetHeader() { + return ListView_GetHeader(window_); +} + +int ListView::GetItemCount() { + return ListView_GetItemCount(window_); +} + +LPARAM ListView::GetItemParam(int i) { + LVITEM lvi = {0}; + lvi.iItem = i; + lvi.mask = LVIF_PARAM; + + if (ListView_GetItem(window_, &lvi)) { + return lvi.lParam; + } else { + return 0; + } +} + +void ListView::GetItemText(int item, int subitem, + LPWSTR output, int max_length) { + ListView_GetItemText(window_, item, subitem, output, max_length); +} + +void ListView::GetItemText(int item, int subitem, + std::wstring& output, int max_length) { + std::vector buffer(max_length); + ListView_GetItemText(window_, item, subitem, &buffer[0], max_length); + output.assign(&buffer[0]); +} + +INT ListView::GetNextItem(int start, UINT flags) { + return ListView_GetNextItem(window_, start, flags); +} + +INT ListView::GetNextItemIndex(int item, int group, LPARAM flags) { + LVITEMINDEX lvii; + lvii.iItem = item; + lvii.iGroup = group; + + if (ListView_GetNextItemIndex(window_, &lvii, flags)) { + return lvii.iItem; + } else { + return -1; + } +} + +UINT ListView::GetSelectedCount() { + return ListView_GetSelectedCount(window_); +} + +INT ListView::GetSelectionMark() { + return ListView_GetSelectionMark(window_); +} + +BOOL ListView::GetSubItemRect(int item, int subitem, LPRECT rect) { + return ListView_GetSubItemRect(window_, item, subitem, LVIR_BOUNDS, rect); +} + +DWORD ListView::GetView() { + return ListView_GetView(window_); +} + +int ListView::HitTest(bool return_subitem) { + LVHITTESTINFO lvhi; + ::GetCursorPos(&lvhi.pt); + ::ScreenToClient(window_, &lvhi.pt); + ListView_SubItemHitTestEx(window_, &lvhi); + + return return_subitem ? lvhi.iSubItem : lvhi.iItem; +} + +int ListView::HitTestEx(LPLVHITTESTINFO lplvhi) { + ::GetCursorPos(&lplvhi->pt); + ::ScreenToClient(window_, &lplvhi->pt); + ListView_SubItemHitTestEx(window_, lplvhi); + + return lplvhi->iItem; +} + +int ListView::InsertItem(const LVITEM& lvi) { + return ListView_InsertItem(window_, &lvi); +} + +int ListView::InsertItem(int item, int group, int image, UINT column_count, + PUINT columns, LPCWSTR text, LPARAM lParam) { + LVITEM lvi = {0}; + lvi.cColumns = column_count; + lvi.iGroupId = group; + lvi.iImage = image; + lvi.iItem = item; + lvi.lParam = lParam; + lvi.puColumns = columns; + lvi.pszText = const_cast(text); + + if (column_count != 0) + lvi.mask |= LVIF_COLUMNS; + if (group > -1) + lvi.mask |= LVIF_GROUPID; + if (image > -1) + lvi.mask |= LVIF_IMAGE; + if (lParam != 0) + lvi.mask |= LVIF_PARAM; + if (text != nullptr) + lvi.mask |= LVIF_TEXT; + + return ListView_InsertItem(window_, &lvi); +} + +BOOL ListView::RedrawItems(int first, int last, bool repaint) { + BOOL return_value = ListView_RedrawItems(window_, first, last); + + if (return_value && repaint) + ::UpdateWindow(window_); + + return return_value; +} + +void ListView::RemoveAllGroups() { + ListView_RemoveAllGroups(window_); +} + +BOOL ListView::SetBkImage(HBITMAP bitmap, ULONG flags, + int offset_x, int offset_y) { + LVBKIMAGE bki= {0}; + bki.hbm = bitmap; + bki.ulFlags = flags; + bki.xOffsetPercent = offset_x; + bki.yOffsetPercent = offset_y; + + return ListView_SetBkImage(window_, &bki); +} + +void ListView::SetCheckState(int index, BOOL check) { + UINT state = INDEXTOSTATEIMAGEMASK((check == TRUE) ? 2 : 1); + ListView_SetItemState(window_, index, state, LVIS_STATEIMAGEMASK); +} + +BOOL ListView::SetColumnWidth(int column, int cx) { + // LVSCW_AUTOSIZE or LVSCW_AUTOSIZE_USEHEADER can be used as cx + return ListView_SetColumnWidth(window_, column, cx); +} + +void ListView::SetExtendedStyle(DWORD ex_style) { + ListView_SetExtendedListViewStyle(window_, ex_style); +} + +DWORD ListView::SetHoverTime(DWORD hover_time) { + return SendMessage(LVM_SETHOVERTIME, 0, hover_time); +} + +void ListView::SetImageList(HIMAGELIST image_list, int type) { + ListView_SetImageList(window_, image_list, type); +} + +BOOL ListView::SetItem(int index, int subitem, LPCWSTR text) { + LVITEM lvi = {0}; + lvi.iItem = index; + lvi.iSubItem = subitem; + lvi.mask = LVIF_TEXT; + lvi.pszText = const_cast(text); + + return ListView_SetItem(window_, &lvi); +} + +BOOL ListView::SetItemIcon(int index, int icon) { + LVITEM lvi = {0}; + lvi.iImage = icon; + lvi.iItem = index; + lvi.mask = LVIF_IMAGE; + + return ListView_SetItem(window_, &lvi); +} + +void ListView::SetSelectedItem(int index) { + ListView_SetItemState(window_, index, LVIS_SELECTED, LVIS_SELECTED); +} + +BOOL ListView::SetTileViewInfo(PLVTILEVIEWINFO tvi) { + return ListView_SetTileViewInfo(window_, tvi); +} + +BOOL ListView::SetTileViewInfo(int line_count, DWORD flags, + RECT* rc_label_margin, SIZE* size_tile) { + LVTILEVIEWINFO tvi = {0}; + tvi.cbSize = sizeof(LVTILEVIEWINFO); + + tvi.dwFlags = flags; + + if (line_count) { + tvi.dwMask |= LVTVIM_COLUMNS; + tvi.cLines = line_count; + } + if (size_tile) { + tvi.dwMask |= LVTVIM_TILESIZE; + tvi.sizeTile = *size_tile; + } + if (rc_label_margin) { + tvi.dwMask |= LVTVIM_LABELMARGIN; + tvi.rcLabelMargin = *rc_label_margin; + } + + return ListView_SetTileViewInfo(window_, &tvi); +} + +int ListView::SetView(DWORD view) { + return ListView_SetView(window_, view); +} + +//////////////////////////////////////////////////////////////////////////////// + +int ListView::GetSortColumn() { + return sort_column_; +} + +int ListView::GetSortOrder() { + return sort_order_; +} + +int ListView::GetSortType() { + return sort_type_; +} + +void ListView::Sort(int sort_column, int sort_order, int type, + PFNLVCOMPARE compare) { + sort_column_ = sort_column; + sort_order_ = (sort_order == 0) ? 1 : sort_order; + sort_type_ = type; + + ListView_SortItemsEx(window_, compare, this); + + if (GetVersion() < kVersionVista) { + HDITEM hdi = {0}; + hdi.mask = HDI_FORMAT; + + HWND header = ListView_GetHeader(window_); + for (int i = 0; i < Header_GetItemCount(header); ++i) { + Header_GetItem(header, i, &hdi); + hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP); + Header_SetItem(header, i, &hdi); + } + + Header_GetItem(header, sort_column, &hdi); + hdi.fmt |= (sort_order > -1 ? HDF_SORTUP : HDF_SORTDOWN); + Header_SetItem(header, sort_column, &hdi); + } +} + +} // namespace win \ No newline at end of file diff --git a/win32/win_ctl_progressbar.cpp b/src/win/ctrl/win_ctrl_progressbar.cpp similarity index 57% rename from win32/win_ctl_progressbar.cpp rename to src/win/ctrl/win_ctrl_progressbar.cpp index 1d7e5d57e..4fd602244 100644 --- a/win32/win_ctl_progressbar.cpp +++ b/src/win/ctrl/win_ctrl_progressbar.cpp @@ -1,62 +1,65 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_control.h" - -namespace win32 { - -// ============================================================================= - -void ProgressBar::PreCreate(CREATESTRUCT &cs) { - cs.dwExStyle = 0; - cs.lpszClass = PROGRESS_CLASS; - cs.style = WS_CHILD | WS_VISIBLE; -} - -void ProgressBar::OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { - Window::OnCreate(hwnd, lpCreateStruct); -} - -// ============================================================================= - -UINT ProgressBar::GetPosition() { - return ::SendMessage(m_hWindow, PBM_GETPOS, 0, 0); -} - -void ProgressBar::SetMarquee(bool enabled) { - if (enabled) { - SetStyle(PBS_MARQUEE, 0); - } else { - SetStyle(0, PBS_MARQUEE); - } - ::SendMessage(m_hWindow, PBM_SETMARQUEE, enabled, 0); -} - -UINT ProgressBar::SetPosition(UINT position) { - return ::SendMessage(m_hWindow, PBM_SETPOS, position, 0); -} - -DWORD ProgressBar::SetRange(UINT min, UINT max) { - return ::SendMessage(m_hWindow, PBM_SETRANGE32, min, max); -} - -UINT ProgressBar::SetState(UINT state) { - return ::SendMessage(m_hWindow, PBM_SETSTATE, state, 0); -} - -} // namespace win32 \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_ctrl.h" + +namespace win { + +ProgressBar::ProgressBar(HWND hwnd) { + SetWindowHandle(hwnd); +} + +void ProgressBar::PreCreate(CREATESTRUCT &cs) { + cs.dwExStyle = 0; + cs.lpszClass = PROGRESS_CLASS; + cs.style = WS_CHILD | WS_VISIBLE; +} + +void ProgressBar::OnCreate(HWND hwnd, LPCREATESTRUCT create_struct) { + Window::OnCreate(hwnd, create_struct); +} + +//////////////////////////////////////////////////////////////////////////////// + +UINT ProgressBar::GetPosition() { + return SendMessage(PBM_GETPOS, 0, 0); +} + +void ProgressBar::SetMarquee(bool enabled) { + if (enabled) { + SetStyle(PBS_MARQUEE, 0); + } else { + SetStyle(0, PBS_MARQUEE); + } + + SendMessage(PBM_SETMARQUEE, enabled, 0); +} + +UINT ProgressBar::SetPosition(UINT position) { + return SendMessage(PBM_SETPOS, position, 0); +} + +DWORD ProgressBar::SetRange(UINT min, UINT max) { + return SendMessage(PBM_SETRANGE32, min, max); +} + +UINT ProgressBar::SetState(UINT state) { + return SendMessage(PBM_SETSTATE, state, 0); +} + +} // namespace win \ No newline at end of file diff --git a/src/win/ctrl/win_ctrl_rebar.cpp b/src/win/ctrl/win_ctrl_rebar.cpp new file mode 100644 index 000000000..10bb76028 --- /dev/null +++ b/src/win/ctrl/win_ctrl_rebar.cpp @@ -0,0 +1,71 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_ctrl.h" + +namespace win { + +Rebar::Rebar(HWND hwnd) { + SetWindowHandle(hwnd); +} + +void Rebar::PreCreate(CREATESTRUCT &cs) { + cs.dwExStyle = 0; + cs.lpszClass = REBARCLASSNAME; + cs.style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | + CCS_NODIVIDER | RBS_BANDBORDERS | RBS_VARHEIGHT; +} + +void Rebar::OnCreate(HWND hwnd, LPCREATESTRUCT create_struct) { + Window::OnCreate(hwnd, create_struct); +} + +//////////////////////////////////////////////////////////////////////////////// + +UINT Rebar::GetBarHeight() { + return SendMessage(RB_GETBARHEIGHT, 0, 0); +} + +BOOL Rebar::InsertBand(LPREBARBANDINFO bar_info) { + return SendMessage(RB_INSERTBAND, -1, reinterpret_cast(bar_info)); +} + +BOOL Rebar::InsertBand(HWND hwnd_child, + UINT cx, UINT cx_header, UINT cx_ideal, + UINT cx_min_child, + UINT cy_child, UINT cy_integral, + UINT cy_max_child, UINT cy_min_child, + UINT mask, UINT style) { + REBARBANDINFO rbi = {0}; + rbi.cbSize = REBARBANDINFO_V6_SIZE; + rbi.cx = cx; + rbi.cxHeader = cx_header; + rbi.cxIdeal = cx_ideal; + rbi.cxMinChild = cx_min_child; + rbi.cyChild = cy_child; + rbi.cyIntegral = cy_integral; + rbi.cyMaxChild = cy_max_child; + rbi.cyMinChild = cy_min_child; + rbi.fMask = mask; + rbi.fStyle = style; + rbi.hwndChild = hwnd_child; + + return SendMessage(RB_INSERTBAND, -1, reinterpret_cast(&rbi)); +} + +} // namespace win \ No newline at end of file diff --git a/src/win/ctrl/win_ctrl_richedit.cpp b/src/win/ctrl/win_ctrl_richedit.cpp new file mode 100644 index 000000000..023f6dcec --- /dev/null +++ b/src/win/ctrl/win_ctrl_richedit.cpp @@ -0,0 +1,94 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_ctrl.h" + +namespace win { + +RichEdit::RichEdit() { + module_ = ::LoadLibrary(L"riched20.dll"); +} + +RichEdit::RichEdit(HWND hwnd) { + module_ = ::LoadLibrary(L"riched20.dll"); + SetWindowHandle(hwnd); +} + +RichEdit::~RichEdit() { + if (module_) + ::FreeLibrary(module_); +} + +void RichEdit::PreCreate(CREATESTRUCT &cs) { + cs.dwExStyle = WS_EX_STATICEDGE; + cs.lpszClass = RICHEDIT_CLASS; + cs.style = WS_CHILD | WS_CLIPCHILDREN | WS_TABSTOP | WS_VISIBLE | WS_VSCROLL | + ES_AUTOHSCROLL; +} + +void RichEdit::OnCreate(HWND hwnd, LPCREATESTRUCT create_struct) { + Window::OnCreate(hwnd, create_struct); +} + +//////////////////////////////////////////////////////////////////////////////// + +void RichEdit::GetSel(CHARRANGE* cr) { + SendMessage(EM_EXGETSEL, 0, reinterpret_cast(cr)); +} + +std::wstring RichEdit::GetTextRange(CHARRANGE* cr) { + std::wstring text(1024, L'\0'); + + TEXTRANGE tr; + tr.chrg.cpMax = cr->cpMax; + tr.chrg.cpMin = cr->cpMin; + tr.lpstrText = const_cast(text.data()); + + SendMessage(EM_GETTEXTRANGE , 0, reinterpret_cast(&tr)); + + return text; +} + +void RichEdit::HideSelection(BOOL hide) { + SendMessage(EM_HIDESELECTION, hide, 0); +} + +BOOL RichEdit::SetCharFormat(DWORD format, CHARFORMAT* cf) { + return SendMessage(EM_SETCHARFORMAT, format, reinterpret_cast(cf)); +} + +DWORD RichEdit::SetEventMask(DWORD flags) { + return SendMessage(EM_SETEVENTMASK, 0, flags); +} + +void RichEdit::SetSel(int start, int end) { + SendMessage(EM_SETSEL, start, end); +} + +void RichEdit::SetSel(CHARRANGE* cr) { + SendMessage(EM_EXSETSEL, 0, reinterpret_cast(cr)); +} + +UINT RichEdit::SetTextEx(const std::string& text) { + SETTEXTEX ste; + return SendMessage(EM_SETTEXTEX, + reinterpret_cast(&ste), + reinterpret_cast(text.data())); +} + +} // namespace win \ No newline at end of file diff --git a/win32/win_ctl_spin.cpp b/src/win/ctrl/win_ctrl_spin.cpp similarity index 59% rename from win32/win_ctl_spin.cpp rename to src/win/ctrl/win_ctrl_spin.cpp index 4374659f9..45e67d1c3 100644 --- a/win32/win_ctl_spin.cpp +++ b/src/win/ctrl/win_ctrl_spin.cpp @@ -1,43 +1,46 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_control.h" - -namespace win32 { - -// ============================================================================= - -bool Spin::GetPos32(int& value) { - BOOL result; - value = ::SendMessage(m_hWindow, UDM_GETPOS32, 0, reinterpret_cast(&result)); - return result == 0; -} - -HWND Spin::SetBuddy(HWND hwnd) { - return reinterpret_cast(::SendMessage(m_hWindow, UDM_SETBUDDY, reinterpret_cast(hwnd), 0)); -} - -int Spin::SetPos32(int position) { - return ::SendMessage(m_hWindow, UDM_SETPOS32, 0, position); -} - -void Spin::SetRange32(int lower_limit, int upper_limit) { - ::SendMessage(m_hWindow, UDM_SETRANGE32, lower_limit, upper_limit); -} - -} // namespace win32 \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_ctrl.h" + +namespace win { + +Spin::Spin(HWND hwnd) { + SetWindowHandle(hwnd); +} + +bool Spin::GetPos32(int& value) { + BOOL result; + value = SendMessage(UDM_GETPOS32, 0, reinterpret_cast(&result)); + return result == 0; +} + +HWND Spin::SetBuddy(HWND hwnd) { + return reinterpret_cast(SendMessage( + UDM_SETBUDDY, reinterpret_cast(hwnd), 0)); +} + +int Spin::SetPos32(int position) { + return SendMessage(UDM_SETPOS32, 0, position); +} + +void Spin::SetRange32(int lower_limit, int upper_limit) { + SendMessage(UDM_SETRANGE32, lower_limit, upper_limit); +} + +} // namespace win \ No newline at end of file diff --git a/src/win/ctrl/win_ctrl_statusbar.cpp b/src/win/ctrl/win_ctrl_statusbar.cpp new file mode 100644 index 000000000..fb8f42be9 --- /dev/null +++ b/src/win/ctrl/win_ctrl_statusbar.cpp @@ -0,0 +1,95 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_ctrl.h" + +namespace win { + +StatusBar::StatusBar(HWND hwnd) { + SetWindowHandle(hwnd); +} + +void StatusBar::PreCreate(CREATESTRUCT &cs) { + cs.dwExStyle = 0; + cs.lpszClass = STATUSCLASSNAME; + cs.style = WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP | SBARS_TOOLTIPS; +} + +void StatusBar::OnCreate(HWND hwnd, LPCREATESTRUCT create_struct) { + widths_.clear(); + Window::OnCreate(hwnd, create_struct); +} + +//////////////////////////////////////////////////////////////////////////////// + +void StatusBar::GetRect(int part, LPRECT rect) { + SendMessage(SB_GETRECT, part, reinterpret_cast(rect)); +} + +int StatusBar::InsertPart(int image, int style, int autosize, int width, + LPCWSTR text, LPCWSTR tooltip) { + if (widths_.empty()) { + widths_.push_back(width); + } else { + widths_.push_back(widths_.back() + width); + } + + int part_count = SendMessage(SB_GETPARTS, 0, 0); + + SendMessage(SB_SETPARTS, part_count + 1, + reinterpret_cast(&widths_[0])); + SendMessage(SB_SETTEXT, part_count - 1, + reinterpret_cast(text)); + SendMessage(SB_SETTIPTEXT, part_count - 1, + reinterpret_cast(tooltip)); + + if (image > -1 && image_list_) { + HICON icon = ::ImageList_GetIcon(image_list_, image, 0); + SendMessage(SB_SETICON, part_count - 1, reinterpret_cast(icon)); + } + + return part_count; +} + +void StatusBar::SetImageList(HIMAGELIST image_list) { + image_list_ = image_list; +} + +void StatusBar::SetPartText(int part, LPCWSTR text) { + SendMessage(SB_SETTEXT, part, reinterpret_cast(text)); +} + +void StatusBar::SetPartTipText(int part, LPCWSTR tip_text) { + SendMessage(SB_SETTIPTEXT, part, reinterpret_cast(tip_text)); +} + +void StatusBar::SetPartWidth(int part, int width) { + if (part > static_cast(widths_.size()) - 1) + return; + + if (part == 0) { + widths_.at(part) = width; + } else { + widths_.at(part) = widths_.at(part - 1) + width; + } + + SendMessage(SB_SETPARTS, widths_.size(), + reinterpret_cast(&widths_[0])); +} + +} // namespace win \ No newline at end of file diff --git a/win32/win_ctl_syslink.cpp b/src/win/ctrl/win_ctrl_syslink.cpp similarity index 70% rename from win32/win_ctl_syslink.cpp rename to src/win/ctrl/win_ctrl_syslink.cpp index 33212322f..576115615 100644 --- a/win32/win_ctl_syslink.cpp +++ b/src/win/ctrl/win_ctrl_syslink.cpp @@ -1,36 +1,38 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_control.h" - -namespace win32 { - -// ============================================================================= - -void SysLink::SetItemState(int item, UINT states) { - LITEM li = {0}; - - li.iLink = item; - li.mask = LIF_ITEMINDEX | LIF_STATE; - li.state = states; - li.stateMask = states; - - ::SendMessage(m_hWindow, LM_SETITEM, 0, reinterpret_cast(&li)); -} - -} // namespace win32 \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_ctrl.h" + +namespace win { + +SysLink::SysLink(HWND hwnd) { + SetWindowHandle(hwnd); +} + +void SysLink::SetItemState(int item, UINT states) { + LITEM li = {0}; + + li.iLink = item; + li.mask = LIF_ITEMINDEX | LIF_STATE; + li.state = states; + li.stateMask = states; + + SendMessage(LM_SETITEM, 0, reinterpret_cast(&li)); +} + +} // namespace win \ No newline at end of file diff --git a/src/win/ctrl/win_ctrl_tab.cpp b/src/win/ctrl/win_ctrl_tab.cpp new file mode 100644 index 000000000..341471f8c --- /dev/null +++ b/src/win/ctrl/win_ctrl_tab.cpp @@ -0,0 +1,111 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_ctrl.h" + +namespace win { + +Tab::Tab(HWND hwnd) { + SetWindowHandle(hwnd); +} + +void Tab::PreCreate(CREATESTRUCT &cs) { + cs.dwExStyle = 0; + cs.lpszClass = WC_TABCONTROL; + cs.style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | TCS_TOOLTIPS; +} + +void Tab::OnCreate(HWND hwnd, LPCREATESTRUCT create_struct) { + TabCtrl_SetExtendedStyle(hwnd, TCS_EX_REGISTERDROP); + Window::OnCreate(hwnd, create_struct); +} + +//////////////////////////////////////////////////////////////////////////////// + +void Tab::AdjustRect(HWND hwnd, BOOL larger, LPRECT rect) { + if (hwnd) { + RECT window_rect; + ::GetClientRect(window_, rect); + ::GetWindowRect(window_, &window_rect); + ::ScreenToClient(hwnd, (LPPOINT)&window_rect); + ::SetRect(rect, + window_rect.left, + window_rect.top, + window_rect.left + rect->right, + window_rect.top + rect->bottom); + } + + TabCtrl_AdjustRect(window_, larger, rect); +} + +int Tab::DeleteAllItems() { + return TabCtrl_DeleteAllItems(window_); +} + +int Tab::DeleteItem(int index) { + return TabCtrl_DeleteItem(window_, index); +} + +int Tab::InsertItem(int index, LPCWSTR text, LPARAM param) { + TCITEM tci; + + tci.mask = TCIF_PARAM | TCIF_TEXT; + tci.pszText = const_cast(text); + tci.lParam = param; + tci.iImage = -1; + + return TabCtrl_InsertItem(window_, index, &tci); +} + +int Tab::GetCurrentlySelected() { + return TabCtrl_GetCurSel(window_); +} + +int Tab::GetItemCount() { + return TabCtrl_GetItemCount(window_); +} + +LPARAM Tab::GetItemParam(int index) { + TCITEM tci; + tci.mask = TCIF_PARAM; + TabCtrl_GetItem(window_, index, &tci); + + return tci.lParam; +} + +int Tab::HitTest() { + TCHITTESTINFO tchti; + ::GetCursorPos(&tchti.pt); + ::ScreenToClient(window_, &tchti.pt); + + return TabCtrl_HitTest(window_, &tchti); +} + +int Tab::SetCurrentlySelected(int item) { + return TabCtrl_SetCurSel(window_, item); +} + +int Tab::SetItemText(int item, LPCWSTR text) { + TCITEM tci; + tci.mask = TCIF_TEXT; + tci.pszText = const_cast(text); + + return TabCtrl_SetItem(window_, item, &tci); +} + +} // namespace win \ No newline at end of file diff --git a/src/win/ctrl/win_ctrl_toolbar.cpp b/src/win/ctrl/win_ctrl_toolbar.cpp new file mode 100644 index 000000000..02a616c37 --- /dev/null +++ b/src/win/ctrl/win_ctrl_toolbar.cpp @@ -0,0 +1,153 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_ctrl.h" + +namespace win { + +Toolbar::Toolbar(HWND hwnd) { + SetWindowHandle(hwnd); +} + +void Toolbar::PreCreate(CREATESTRUCT &cs) { + cs.dwExStyle = 0; + cs.lpszClass = TOOLBARCLASSNAME; + cs.style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | + CCS_NODIVIDER | CCS_NOPARENTALIGN | + TBSTYLE_FLAT | TBSTYLE_LIST | TBSTYLE_TOOLTIPS | + TBSTYLE_TRANSPARENT; +} + +void Toolbar::OnCreate(HWND hwnd, LPCREATESTRUCT create_struct) { + SendMessage(TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0); + SendMessage(TB_SETEXTENDEDSTYLE, 0, + TBSTYLE_EX_DRAWDDARROWS | TBSTYLE_EX_MIXEDBUTTONS); + SetImageList(nullptr, 0, 0); + Window::OnCreate(hwnd, create_struct); +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL Toolbar::EnableButton(int command_id, bool enabled) { + TBBUTTONINFO tbbi = {0}; + tbbi.cbSize = sizeof(TBBUTTONINFO); + tbbi.dwMask = TBIF_STATE; + + SendMessage(TB_GETBUTTONINFO, command_id, reinterpret_cast(&tbbi)); + + if (enabled) { + tbbi.fsState |= TBSTATE_ENABLED; + tbbi.fsState &= ~TBSTATE_INDETERMINATE; + } else { + tbbi.fsState |= TBSTATE_INDETERMINATE; + tbbi.fsState &= ~TBSTATE_ENABLED; + } + + return SendMessage(TB_SETBUTTONINFO, command_id, + reinterpret_cast(&tbbi)); +} + +int Toolbar::GetHeight() { + RECT rect; + SendMessage(TB_GETITEMRECT, 1, reinterpret_cast(&rect)); + return rect.bottom; +} + +BOOL Toolbar::GetButton(int index, TBBUTTON& tbb) { + return SendMessage(TB_GETBUTTON, index, reinterpret_cast(&tbb)); +} + +int Toolbar::GetButtonCount() { + return SendMessage(TB_BUTTONCOUNT, 0, 0); +} + +DWORD Toolbar::GetButtonSize() { + return SendMessage(TB_GETBUTTONSIZE, 0, 0); +} + +DWORD Toolbar::GetButtonStyle(int index) { + TBBUTTON tbb = {0}; + SendMessage(TB_GETBUTTON, index, reinterpret_cast(&tbb)); + return tbb.fsStyle; +} + +LPCWSTR Toolbar::GetButtonTooltip(int index) { + bool valid = index < static_cast(tooltip_text_.size()); + return valid ? tooltip_text_[index] : L""; +} + +DWORD Toolbar::GetPadding() { + return SendMessage(TB_GETPADDING, 0, 0); +} + +int Toolbar::HitTest(POINT& point) { + return SendMessage(TB_HITTEST, 0, reinterpret_cast(&point)); +} + +BOOL Toolbar::InsertButton(int index, int bitmap, int command_id, + BYTE state, BYTE style, DWORD_PTR data, + LPCWSTR text, LPCWSTR tooltip) { + TBBUTTON tbb = {0}; + tbb.iBitmap = bitmap; + tbb.idCommand = command_id; + tbb.iString = reinterpret_cast(text); + tbb.fsState = state; + tbb.fsStyle = style; + tbb.dwData = data; + + tooltip_text_.push_back(tooltip); + return SendMessage(TB_INSERTBUTTON, index, reinterpret_cast(&tbb)); +} + +BOOL Toolbar::PressButton(int command_id, BOOL press) { + return SendMessage(TB_PRESSBUTTON, command_id, MAKELPARAM(press, 0)); +} + +BOOL Toolbar::SetButtonImage(int index, int image) { + TBBUTTONINFO tbbi = {0}; + tbbi.cbSize = sizeof(TBBUTTONINFO); + tbbi.dwMask = TBIF_BYINDEX | TBIF_IMAGE; + tbbi.iImage = image; + + return SendMessage(TB_SETBUTTONINFO, index, reinterpret_cast(&tbbi)); +} + +BOOL Toolbar::SetButtonText(int index, LPCWSTR text) { + TBBUTTONINFO tbbi = {0}; + tbbi.cbSize = sizeof(TBBUTTONINFO); + tbbi.dwMask = TBIF_BYINDEX | TBIF_TEXT; + tbbi.pszText = const_cast(text); + + return SendMessage(TB_SETBUTTONINFO, index, reinterpret_cast(&tbbi)); +} + +BOOL Toolbar::SetButtonTooltip(int index, LPCWSTR tooltip) { + if (index > static_cast(tooltip_text_.size())) { + return FALSE; + } else { + tooltip_text_[index] = tooltip; + return TRUE; + } +} + +void Toolbar::SetImageList(HIMAGELIST image_list, int dx, int dy) { + SendMessage(TB_SETBITMAPSIZE, 0, MAKELONG(dx, dy)); + SendMessage(TB_SETIMAGELIST, 0, reinterpret_cast(image_list)); +} + +} // namespace win \ No newline at end of file diff --git a/src/win/ctrl/win_ctrl_tooltip.cpp b/src/win/ctrl/win_ctrl_tooltip.cpp new file mode 100644 index 000000000..d91c4a562 --- /dev/null +++ b/src/win/ctrl/win_ctrl_tooltip.cpp @@ -0,0 +1,95 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_ctrl.h" + +namespace win { + +Tooltip::Tooltip(HWND hwnd) { + SetWindowHandle(hwnd); +} + +void Tooltip::PreCreate(CREATESTRUCT &cs) { + cs.dwExStyle = 0; + cs.lpszClass = TOOLTIPS_CLASS; + cs.style = WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP; +} + +void Tooltip::OnCreate(HWND hwnd, LPCREATESTRUCT create_struct) { + ::SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + Window::OnCreate(hwnd, create_struct); +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL Tooltip::AddTip(UINT id, LPCWSTR text, LPCWSTR title, LPRECT rect, + bool window_id) { + TOOLINFO ti; + ti.cbSize = sizeof(TOOLINFO); + ti.hwnd = parent_; + ti.hinst = instance_; + ti.lpszText = const_cast(text); + ti.uFlags = TTF_SUBCLASS | (window_id ? TTF_IDISHWND : 0); + ti.uId = (UINT_PTR)id; + + if (rect) + ti.rect = *rect; + + BOOL result = SendMessage(TTM_ADDTOOL, 0, reinterpret_cast(&ti)); + if (result && title) + result = SendMessage(TTM_SETTITLE, 1, reinterpret_cast(title)); + + return result; +} + +BOOL Tooltip::DeleteTip(UINT id) { + TOOLINFO ti; + ti.cbSize = sizeof(TOOLINFO); + ti.hwnd = parent_; + ti.uId = static_cast(id); + + return SendMessage(TTM_DELTOOL, 0, reinterpret_cast(&ti)); +} + +void Tooltip::SetDelayTime(long autopop, long initial, long reshow) { + SendMessage(TTM_SETDELAYTIME, TTDT_AUTOPOP, autopop); + SendMessage(TTM_SETDELAYTIME, TTDT_INITIAL, initial); + SendMessage(TTM_SETDELAYTIME, TTDT_RESHOW, reshow); +} + +void Tooltip::SetMaxWidth(long width) { + SendMessage(TTM_SETMAXTIPWIDTH, 0, width); +} + +void Tooltip::UpdateText(UINT id, LPCWSTR text) { + TOOLINFO ti; + ti.cbSize = sizeof(TOOLINFO); + ti.hinst = instance_; + ti.hwnd = parent_; + ti.lpszText = const_cast(text); + ti.uId = static_cast(id); + + SendMessage(TTM_UPDATETIPTEXT, 0, reinterpret_cast(&ti)); +} + +void Tooltip::UpdateTitle(LPCWSTR title) { + SendMessage(TTM_SETTITLE, title ? 0 : 1, reinterpret_cast(title)); +} + +} // namespace win \ No newline at end of file diff --git a/src/win/ctrl/win_ctrl_treeview.cpp b/src/win/ctrl/win_ctrl_treeview.cpp new file mode 100644 index 000000000..ac77e992b --- /dev/null +++ b/src/win/ctrl/win_ctrl_treeview.cpp @@ -0,0 +1,143 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_ctrl.h" + +namespace win { + +TreeView::TreeView(HWND hwnd) { + SetWindowHandle(hwnd); +} + +void TreeView::PreCreate(CREATESTRUCT &cs) { + cs.dwExStyle = WS_EX_CLIENTEDGE; + cs.lpszClass = WC_TREEVIEW; + cs.style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | + TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT; +} + +void TreeView::OnCreate(HWND hwnd, LPCREATESTRUCT create_struct) { + TreeView_SetExtendedStyle(hwnd, TVS_EX_DOUBLEBUFFER, 0); + Window::OnCreate(hwnd, create_struct); +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL TreeView::DeleteAllItems() { + return TreeView_DeleteAllItems(window_); +} + +BOOL TreeView::DeleteItem(HTREEITEM item) { + return TreeView_DeleteItem(window_, item); +} + +BOOL TreeView::Expand(HTREEITEM item, bool expand) { + return TreeView_Expand(window_, item, expand ? TVE_EXPAND : TVE_COLLAPSE); +} + +UINT TreeView::GetCheckState(HTREEITEM item) { + return TreeView_GetCheckState(window_, item); +} + +UINT TreeView::GetCount() { + return TreeView_GetCount(window_); +} + +BOOL TreeView::GetItem(LPTVITEM item) { + return TreeView_GetItem(window_, item); +} + +LPARAM TreeView::GetItemData(HTREEITEM item) { + TVITEM tvi = {0}; + tvi.mask = TVIF_PARAM; + tvi.hItem = item; + TreeView_GetItem(window_, &tvi); + + return tvi.lParam; +} + +HTREEITEM TreeView::GetSelection() { + return TreeView_GetSelection(window_); +} + +HTREEITEM TreeView::HitTest(LPTVHITTESTINFO hti, bool get_cursor_pos) { + if (get_cursor_pos) { + GetCursorPos(&hti->pt); + ScreenToClient(window_, &hti->pt); + } + + return TreeView_HitTest(window_, hti); +} + +HTREEITEM TreeView::InsertItem(LPCWSTR text, int image, LPARAM param, + HTREEITEM parent, HTREEITEM insert_after) { + TVITEM tvi = {0}; + tvi.mask = TVIF_TEXT | TVIF_PARAM; + tvi.pszText = const_cast(text); + tvi.lParam = param; + + if (image > -1) { + tvi.mask |= TVIF_IMAGE | TVIF_SELECTEDIMAGE; + tvi.iImage = image; + tvi.iSelectedImage = image; + } + + TVINSERTSTRUCT tvis = {0}; + tvis.item = tvi; + tvis.hInsertAfter = insert_after; + tvis.hParent = parent; + + return TreeView_InsertItem(window_, &tvis); +} + +BOOL TreeView::SelectItem(HTREEITEM item) { + return TreeView_Select(window_, item, TVGN_CARET); +} + +UINT TreeView::SetCheckState(HTREEITEM item, BOOL check) { + TVITEM tvi = {0}; + tvi.mask = TVIF_HANDLE | TVIF_STATE; + tvi.hItem = item; + tvi.stateMask = TVIS_STATEIMAGEMASK; + tvi.state = INDEXTOSTATEIMAGEMASK(check + 1); + + return TreeView_SetItem(window_, &tvi); +} + +HIMAGELIST TreeView::SetImageList(HIMAGELIST image_list, INT image) { + return TreeView_SetImageList(window_, image_list, image); +} + +BOOL TreeView::SetItem(HTREEITEM item, LPCWSTR text) { + TVITEM tvi = {0}; + tvi.mask = TVIF_HANDLE; + tvi.hItem = item; + + if (!TreeView_GetItem(window_, &tvi)) + return FALSE; + + tvi.mask |= TVIF_TEXT; + tvi.pszText = const_cast(text); + return TreeView_SetItem(window_, &tvi); +} + +int TreeView::SetItemHeight(SHORT cyItem) { + return TreeView_SetItemHeight(window_, cyItem); +} + +} // namespace win \ No newline at end of file diff --git a/src/win/win_commondialog.cpp b/src/win/win_commondialog.cpp new file mode 100644 index 000000000..12de1525d --- /dev/null +++ b/src/win/win_commondialog.cpp @@ -0,0 +1,171 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include + +#include "win/win_commondialog.h" + +#ifdef _MSC_VER +#pragma warning (disable: 4996) +#endif + +namespace win { + +std::wstring BrowseForFile(HWND hwnd_owner, + const std::wstring& title, + std::wstring filter) { + if (filter.empty()) + filter = L"All files (*.*)\0*.*\0"; + + WCHAR file[MAX_PATH] = {'\0'}; + + OPENFILENAME ofn = {0}; + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = hwnd_owner; + ofn.lpstrFile = file; + ofn.lpstrFilter = filter.c_str(); + ofn.lpstrTitle = title.c_str(); + ofn.nMaxFile = sizeof(file); + ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST; + + if (GetOpenFileName(&ofn)) { + return std::wstring(file); + } else { + return std::wstring(); + } +} + +bool BrowseForFolderVista(HWND hwnd_owner, + const std::wstring& title, + const std::wstring& default_folder, + std::wstring& output) { + IFileDialog* file_dialog; + bool result = false; + + HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, + nullptr, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&file_dialog)); + + if (SUCCEEDED(hr)) { + FILEOPENDIALOGOPTIONS options; + file_dialog->GetOptions(&options); + options |= FOS_PICKFOLDERS; + file_dialog->SetOptions(options); + + if (!title.empty()) + file_dialog->SetTitle(title.c_str()); + + if (!default_folder.empty()) { + IShellItem* shell_item; + HRESULT hr = 0; + + typedef HRESULT (WINAPI* _SHCreateItemFromParsingName)( + PCWSTR pszPath, IBindCtx* pbc, REFIID riid, void** ppv); + HMODULE module = LoadLibrary(L"shell32.dll"); + if (module != nullptr) { + auto proc = reinterpret_cast<_SHCreateItemFromParsingName>( + GetProcAddress(module, "SHCreateItemFromParsingName")); + if (proc != nullptr) { + hr = (proc)(default_folder.c_str(), nullptr, IID_IShellItem, + reinterpret_cast(&shell_item)); + } + FreeLibrary(module); + } + + if (SUCCEEDED(hr)) { + file_dialog->SetDefaultFolder(shell_item); + shell_item->Release(); + } + } + + hr = file_dialog->Show(hwnd_owner); + if (SUCCEEDED(hr)) { + IShellItem* shell_item; + hr = file_dialog->GetFolder(&shell_item); + if (SUCCEEDED(hr)) { + LPWSTR path = nullptr; + hr = shell_item->GetDisplayName(SIGDN_FILESYSPATH, &path); + if (SUCCEEDED(hr)) { + output.assign(path); + CoTaskMemFree(path); + result = true; + } + shell_item->Release(); + } + } + + file_dialog->Release(); + } + + return result; +} + +static int CALLBACK BrowseForFolderXpProc( + HWND hwnd_owner, UINT uMsg, LPARAM lParam, LPARAM lpData) { + switch (uMsg) { + case BFFM_INITIALIZED: + if (lpData != 0) + SendMessage(hwnd_owner, BFFM_SETSELECTION, TRUE, lpData); + break; + } + + return 0; +} + +bool BrowseForFolderXp(HWND hwnd_owner, + const std::wstring& title, + const std::wstring& default_path, + std::wstring& output) { + BROWSEINFO bi = {0}; + bi.hwndOwner = hwnd_owner; + bi.ulFlags = BIF_NEWDIALOGSTYLE | BIF_NONEWFOLDERBUTTON; + + if (!title.empty()) + bi.lpszTitle = title.c_str(); + + if (!default_path.empty()) { + WCHAR path[MAX_PATH] = {'\0'}; + default_path.copy(path, MAX_PATH); + bi.lParam = reinterpret_cast(path); + bi.lpfn = BrowseForFolderXpProc; + } + + PIDLIST_ABSOLUTE pidl = SHBrowseForFolder(&bi); + if (pidl == nullptr) + return false; + + WCHAR path[MAX_PATH]; + SHGetPathFromIDList(pidl, path); + output = path; + + return !output.empty(); +} + +bool BrowseForFolder(HWND hwnd_owner, + const std::wstring& title, + const std::wstring& default_path, + std::wstring& output) { + if (win::GetVersion() >= win::kVersionVista) { + return BrowseForFolderVista(hwnd_owner, title, default_path, output); + } else { + return BrowseForFolderXp(hwnd_owner, title, default_path, output); + } +} + +} // namespace win \ No newline at end of file diff --git a/src/win/win_commondialog.h b/src/win/win_commondialog.h new file mode 100644 index 000000000..fc378c804 --- /dev/null +++ b/src/win/win_commondialog.h @@ -0,0 +1,37 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_WIN_COMMONDIALOG_H +#define TAIGA_WIN_COMMONDIALOG_H + +#include "win_main.h" + +namespace win { + +std::wstring BrowseForFile(HWND hwnd_owner, + const std::wstring& title, + std::wstring filter); + +bool BrowseForFolder(HWND hwnd_owner, + const std::wstring& title, + const std::wstring& default_path, + std::wstring& output); + +} // namespace win + +#endif // TAIGA_WIN_COMMONDIALOG_H \ No newline at end of file diff --git a/src/win/win_dde.cpp b/src/win/win_dde.cpp new file mode 100644 index 000000000..c282c918d --- /dev/null +++ b/src/win/win_dde.cpp @@ -0,0 +1,194 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_dde.h" + +#include "base/string.h" + +namespace win { + +DynamicDataExchange::DynamicDataExchange() + : conversation_(nullptr), + instance_(0), + is_unicode_(false) { +} + +DynamicDataExchange::~DynamicDataExchange() { + Disconnect(); + UnInitialize(); +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL DynamicDataExchange::Initialize(DWORD afCmd, bool unicode) { + is_unicode_ = unicode; + + if (is_unicode_) { + ::DdeInitializeW(&instance_, DdeCallback, afCmd, 0); + } else { + ::DdeInitializeA(&instance_, DdeCallback, afCmd, 0); + } + + return instance_ != 0; +} + +void DynamicDataExchange::UnInitialize() { + if (instance_) { + ::DdeUninitialize(instance_); + instance_ = 0; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL DynamicDataExchange::Connect(const std::wstring& service, + const std::wstring& topic) { + if (!instance_) + return FALSE; + + HSZ hszService = CreateStringHandle(service); + HSZ hszTopic = CreateStringHandle(topic); + + conversation_ = ::DdeConnect(instance_, hszService, hszTopic, NULL); + + FreeStringHandle(hszTopic); + FreeStringHandle(hszService); + + return conversation_ != nullptr; +} + +void DynamicDataExchange::Disconnect() { + if (conversation_) { + ::DdeDisconnect(conversation_); + conversation_ = nullptr; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL DynamicDataExchange::ClientTransaction(const std::wstring& item, + const std::wstring& data, + std::wstring* output, + UINT wType) { + DWORD dwResult = 0; + HSZ hszItem = wType != XTYP_EXECUTE ? CreateStringHandle(item) : nullptr; + + HDDEDATA hData = ::DdeClientTransaction( + is_unicode_ ? (LPBYTE)data.data() : (LPBYTE)WstrToStr(data).data(), + is_unicode_ ? (data.size() + 1) * sizeof(WCHAR) : data.size() + 1, + conversation_, + hszItem, + is_unicode_ ? CF_UNICODETEXT : CF_TEXT, + wType, + 3000, + &dwResult); + + FreeStringHandle(hszItem); + + if (output) { + char szResult[255]; + ::DdeGetData(hData, (unsigned char*)szResult, 255, 0); + output->assign(StrToWstr(szResult)); + } + + return hData != 0; +} + +BOOL DynamicDataExchange::IsAvailable() { + return instance_ != 0; +} + +BOOL DynamicDataExchange::NameService(const std::wstring& service, UINT afCmd) { + HSZ hszService = CreateStringHandle(service); + HDDEDATA result = ::DdeNameService(instance_, hszService, 0, afCmd); + FreeStringHandle(hszService); + return result != nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// + +HSZ DynamicDataExchange::CreateStringHandle(const std::wstring& str) { + if (is_unicode_) { + return ::DdeCreateStringHandleW(instance_, str.c_str(), CP_WINUNICODE); + } else { + return ::DdeCreateStringHandleA(instance_, WstrToStr(str).c_str(), CP_WINANSI); + } +} + +void DynamicDataExchange::FreeStringHandle(HSZ str) { + if (str) + ::DdeFreeStringHandle(instance_, str); +} + +HDDEDATA CALLBACK DynamicDataExchange::DdeCallback(UINT uType, UINT uFmt, + HCONV hconv, + HSZ hsz1, HSZ hsz2, + HDDEDATA hdata, + DWORD dwData1, DWORD dwData2) { + DWORD cb = 0; + LPVOID lpData = nullptr; + char sz1[256] = {'\0'}; + char sz2[256] = {'\0'}; + //DdeQueryStringA(instance_, hsz1, sz1, 256, CP_WINANSI); + //DdeQueryStringA(instance_, hsz2, sz2, 256, CP_WINANSI); + + switch (uType) { + case XTYP_CONNECT: { + OutputDebugStringA("[CONNECT]\n"); + //BOOL result = OnConnect(); + return reinterpret_cast(TRUE); + } + + case XTYP_POKE: { + if (hdata) + lpData = DdeAccessData(hdata, &cb); +#ifdef _DEBUG + std::string str = "[POKE]"; + str += "Topic: " + *sz1; + str += " - Item: " + *sz2; + if (lpData) str += " - Data: "; str += (LPCSTR)lpData; + str += "\n"; + OutputDebugStringA(str.c_str()); +#endif + //OnPoke(); + if (hdata) + DdeUnaccessData(hdata); + return reinterpret_cast(DDE_FACK); + } + + case XTYP_REQUEST: { + // TODO: Call DdeCreateDataHandle(); +#ifdef _DEBUG + std::string str = "[REQUEST] "; + str += "Topic: "; str += sz1; + str += " - Item: "; str += sz2; + str += "\n"; + OutputDebugStringA(str.c_str()); +#endif + //OnRequest(); + break; + } + + default: + break; + } + + return reinterpret_cast(0); +} + +} // namespace win \ No newline at end of file diff --git a/dde.h b/src/win/win_dde.h similarity index 59% rename from dde.h rename to src/win/win_dde.h index b404cad9c..745456500 100644 --- a/dde.h +++ b/src/win/win_dde.h @@ -1,52 +1,64 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef DDE_H -#define DDE_H - -#include "std.h" - -// ============================================================================= - -class DynamicDataExchange { -public: - DynamicDataExchange(); - ~DynamicDataExchange(); - - BOOL ClientTransaction(const wstring& item, const wstring& data, wstring* output, UINT wType); - BOOL Connect(const wstring& service, const wstring& topic); - void Disconnect(); - BOOL Initialize(DWORD afCmd = APPCLASS_STANDARD | APPCMD_CLIENTONLY, BOOL unicode = FALSE); - BOOL IsAvailable(); - BOOL NameService(const wstring& service, UINT afCmd = DNS_REGISTER); - void UnInitialize(); - - virtual BOOL OnConnect() { return TRUE; } - virtual void OnPoke() {} - virtual void OnRequest() {} - -private: - static FNCALLBACK DdeCallback; - -private: - HCONV conversation_; - DWORD instance_; - BOOL is_unicode_; -}; - -#endif // DDE_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_WIN_DDE_H +#define TAIGA_WIN_DDE_H + +#include "win_main.h" + +namespace win { + +// A helper class to use Dynamic Data Exchange (DDE) protocol + +class DynamicDataExchange { +public: + DynamicDataExchange(); + ~DynamicDataExchange(); + + BOOL Initialize(DWORD afCmd = APPCLASS_STANDARD | APPCMD_CLIENTONLY, + bool unicode = false); + void UnInitialize(); + + BOOL Connect(const std::wstring& service, const std::wstring& topic); + void Disconnect(); + + BOOL ClientTransaction(const std::wstring& item, + const std::wstring& data, + std::wstring* output, + UINT wType); + BOOL IsAvailable(); + BOOL NameService(const std::wstring& service, UINT afCmd = DNS_REGISTER); + + virtual BOOL OnConnect() { return TRUE; } + virtual void OnPoke() {} + virtual void OnRequest() {} + +private: + HSZ CreateStringHandle(const std::wstring& str); + void FreeStringHandle(HSZ str); + + static FNCALLBACK DdeCallback; + + HCONV conversation_; + DWORD instance_; + bool is_unicode_; +}; + +} // namespace win + +#endif // TAIGA_WIN_DDE_H \ No newline at end of file diff --git a/src/win/win_dialog.cpp b/src/win/win_dialog.cpp new file mode 100644 index 000000000..373431b51 --- /dev/null +++ b/src/win/win_dialog.cpp @@ -0,0 +1,398 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_dialog.h" +#include "win_taskbar.h" + +//#define TAIGA_WIN_DIALOG_REMEMBER_LAST_POSITION_AND_SIZE + +namespace win { + +Dialog::Dialog() + : modal_(true), snap_gap_(0) { + size_last_.cx = 0; + size_last_.cy = 0; + + size_max_.cx = 0; + size_max_.cy = 0; + + size_min_.cx = 0; + size_min_.cy = 0; + + pos_last_.x = 0; + pos_last_.y = 0; +} + +Dialog::~Dialog() { + EndDialog(0); +} + +INT_PTR Dialog::Create(UINT resource_id, HWND parent, bool modal) { + modal_ = modal; + + if (modal) { + INT_PTR result = ::DialogBoxParam( + instance_, MAKEINTRESOURCE(resource_id), parent, DialogProcStatic, + reinterpret_cast(this)); + window_ = nullptr; + return result; + + } else { + window_ = ::CreateDialogParam( + instance_, MAKEINTRESOURCE(resource_id), parent, DialogProcStatic, + reinterpret_cast(this)); + return reinterpret_cast(window_); + } +} + +void Dialog::EndDialog(INT_PTR result) { +#ifdef TAIGA_WIN_DIALOG_REMEMBER_LAST_POSITION_AND_SIZE + RECT rect; + GetWindowRect(&rect); + pos_last_.x = rect.left; + pos_last_.y = rect.top; + size_last_.cx = rect.right - rect.left; + size_last_.cy = rect.bottom - rect.top; +#endif + + if (IsWindow()) { + if (modal_) { + ::EndDialog(window_, result); + } else { + Destroy(); + } + } + window_ = nullptr; +} + +void Dialog::SetSizeMax(LONG cx, LONG cy) { + size_max_.cx = cx; + size_max_.cy = cy; +} + +void Dialog::SetSizeMin(LONG cx, LONG cy) { + size_min_.cx = cx; + size_min_.cy = cy; +} + +void Dialog::SetSnapGap(int snap_gap) { + snap_gap_ = snap_gap; +} + +//////////////////////////////////////////////////////////////////////////////// + +void Dialog::SetMinMaxInfo(LPMINMAXINFO mmi) { + if (size_max_.cx > 0) + mmi->ptMaxTrackSize.x = size_max_.cx; + if (size_max_.cy > 0) + mmi->ptMaxTrackSize.y = size_max_.cy; + + if (size_min_.cx > 0) + mmi->ptMinTrackSize.x = size_min_.cx; + if (size_min_.cy > 0) + mmi->ptMinTrackSize.y = size_min_.cy; +} + +void Dialog::SnapToEdges(LPWINDOWPOS window_pos) { + if (!snap_gap_) + return; + + RECT rc_monitor = {0}; + SystemParametersInfo(SPI_GETWORKAREA, 0, &rc_monitor, 0); + if (GetSystemMetrics(SM_CMONITORS) > 1) { + HMONITOR monitor = MonitorFromWindow(window_, MONITOR_DEFAULTTONEAREST); + if (monitor) { + MONITORINFO mi; + mi.cbSize = sizeof(mi); + GetMonitorInfo(monitor, &mi); + rc_monitor = mi.rcWork; + } + } + + // Snap X axis + if (abs(window_pos->x - rc_monitor.left) <= snap_gap_) { + window_pos->x = rc_monitor.left; + } else if (abs(window_pos->x + window_pos->cx - rc_monitor.right) <= snap_gap_) { + window_pos->x = rc_monitor.right - window_pos->cx; + } + // Snap Y axis + if (abs(window_pos->y - rc_monitor.top) <= snap_gap_) { + window_pos->y = rc_monitor.top; + } else if (abs(window_pos->y + window_pos->cy - rc_monitor.bottom) <= snap_gap_) { + window_pos->y = rc_monitor.bottom - window_pos->cy; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void Dialog::OnCancel() { + EndDialog(IDCANCEL); +} + +BOOL Dialog::OnClose() { + return FALSE; +} + +BOOL Dialog::OnInitDialog() { + return TRUE; +} + +void Dialog::OnOK() { + EndDialog(IDOK); +} + +// Superclassing +void Dialog::RegisterDlgClass(LPCWSTR class_name) { + WNDCLASS wc; + ::GetClassInfo(instance_, WC_DIALOG, &wc); // WC_DIALOG is #32770 + wc.lpszClassName = class_name; + ::RegisterClass(&wc); +} + +//////////////////////////////////////////////////////////////////////////////// + +BOOL Dialog::AddComboString(int id_combo, LPCWSTR text) { + return SendDlgItemMessage(id_combo, CB_ADDSTRING, 0, + reinterpret_cast(text)); +} + +BOOL Dialog::CheckDlgButton(int id_button, UINT check) { + return ::CheckDlgButton(window_, id_button, check); +} + +BOOL Dialog::CheckRadioButton(int id_first_button, int id_last_button, + int id_check_button) { + return ::CheckRadioButton(window_, id_first_button, id_last_button, + id_check_button); +} + +BOOL Dialog::EnableDlgItem(int id_item, BOOL enable) { + return ::EnableWindow(GetDlgItem(id_item), enable); +} + +INT Dialog::GetCheckedRadioButton(int id_first_button, int id_last_button) { + for (int i = 0; i <= id_last_button - id_first_button; i++) + if (IsDlgButtonChecked(id_first_button + i)) + return i; + + return 0; +} + +INT Dialog::GetComboSelection(int id_item) { + return SendDlgItemMessage(id_item, CB_GETCURSEL, 0, 0); +} + +HWND Dialog::GetDlgItem(int id_item) { + return ::GetDlgItem(window_, id_item); +} + +UINT Dialog::GetDlgItemInt(int id_item) { + return ::GetDlgItemInt(window_, id_item, nullptr, TRUE); +} + +void Dialog::GetDlgItemText(int id_item, LPWSTR output, int max_length) { + ::GetDlgItemText(window_, id_item, output, max_length); +} + +void Dialog::GetDlgItemText(int id_item, std::wstring& output) { + int len = ::GetWindowTextLength(GetDlgItem(id_item)) + 1; + std::vector buffer(len); + ::GetDlgItemText(window_, id_item, &buffer[0], len); + output.assign(&buffer[0]); +} + +std::wstring Dialog::GetDlgItemText(int id_item) { + int len = ::GetWindowTextLength(GetDlgItem(id_item)) + 1; + std::vector buffer(len); + ::GetDlgItemText(window_, id_item, &buffer[0], len); + return std::wstring(&buffer[0]); +} + +BOOL Dialog::HideDlgItem(int id_item) { + return ::ShowWindow(GetDlgItem(id_item), SW_HIDE); +} + +BOOL Dialog::IsDlgButtonChecked(int id_button) { + return ::IsDlgButtonChecked(window_, id_button); +} + +BOOL Dialog::SendDlgItemMessage(int id_item, UINT uMsg, + WPARAM wParam, LPARAM lParam) { + return ::SendDlgItemMessage(window_, id_item, uMsg, wParam, lParam); +} + +BOOL Dialog::SetComboSelection(int id_item, int index) { + return SendDlgItemMessage(id_item, CB_SETCURSEL, index, 0); +} + +BOOL Dialog::SetDlgItemText(int id_item, LPCWSTR text) { + return ::SetDlgItemText(window_, id_item, text); +} + +BOOL Dialog::ShowDlgItem(int id_item, int cmd_show) { + return ::ShowWindow(GetDlgItem(id_item), cmd_show); +} + +//////////////////////////////////////////////////////////////////////////////// + +INT_PTR CALLBACK Dialog::DialogProcStatic(HWND hwnd, UINT uMsg, + WPARAM wParam, LPARAM lParam) { + Dialog* window = reinterpret_cast(WindowMap.GetWindow(hwnd)); + + if (!window && uMsg == WM_INITDIALOG) { + window = reinterpret_cast(lParam); + if (window) { + window->SetWindowHandle(hwnd); + WindowMap.Add(hwnd, window); + } + } + + if (window) { + return window->DialogProc(hwnd, uMsg, wParam, lParam); + } else { + return FALSE; + } +} + +INT_PTR Dialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + return DialogProcDefault(hwnd, uMsg, wParam, lParam); +} + +INT_PTR Dialog::DialogProcDefault(HWND hwnd, UINT uMsg, + WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_CLOSE: { + return OnClose(); + } + case WM_COMMAND: { + switch (LOWORD(wParam)) { + case IDOK: + OnOK(); + return TRUE; + case IDCANCEL: + OnCancel(); + return TRUE; + default: + return OnCommand(wParam, lParam); + } + break; + } + case WM_DESTROY: { + OnDestroy(); + break; + } + case WM_INITDIALOG: { +#ifdef TAIGA_WIN_DIALOG_REMEMBER_LAST_POSITION_AND_SIZE + if (pos_last_.x && pos_last_.y) + SetPosition(nullptr, pos_last_.x, pos_last_.y, 0, 0, SWP_NOSIZE); + if (size_last_.cx && size_last_.cy) + SetPosition(nullptr, 0, 0, size_last_.cx, size_last_.cy, SWP_NOMOVE); +#endif + return OnInitDialog(); + } + case WM_DROPFILES: { + OnDropFiles(reinterpret_cast(wParam)); + break; + } + case WM_ENTERSIZEMOVE: + case WM_EXITSIZEMOVE: { + SIZE size = {0}; + OnSize(uMsg, 0, size); + break; + } + case WM_GETMINMAXINFO: { + LPMINMAXINFO mmi = reinterpret_cast(lParam); + SetMinMaxInfo(mmi); + OnGetMinMaxInfo(mmi); + break; + } + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_MOUSEACTIVATE: + case WM_MOUSEHOVER: + case WM_MOUSEHWHEEL: + case WM_MOUSELEAVE: + case WM_MOUSEMOVE: + case WM_MOUSEWHEEL: { + LRESULT result = OnMouseEvent(uMsg, wParam, lParam); + if (result != -1) { + ::SetWindowLongPtrW(hwnd, DWL_MSGRESULT, result); + return TRUE; + } + break; + } + case WM_MOVE: { + POINTS pts = MAKEPOINTS(lParam); + OnMove(&pts); + break; + } + case WM_NOTIFY: { + LRESULT result = OnNotify(wParam, reinterpret_cast(lParam)); + if (result) { + ::SetWindowLongPtr(hwnd, DWL_MSGRESULT, result); + return TRUE; + } + break; + } + case WM_PAINT: { + if (::GetUpdateRect(hwnd, nullptr, FALSE)) { + PAINTSTRUCT ps; + HDC hdc = ::BeginPaint(hwnd, &ps); + OnPaint(hdc, &ps); + ::EndPaint(hwnd, &ps); + } else { + HDC hdc = ::GetDC(hwnd); + OnPaint(hdc, nullptr); + ::ReleaseDC(hwnd, hdc); + } + break; + } + case WM_SIZE: { + SIZE size = {LOWORD(lParam), HIWORD(lParam)}; + OnSize(uMsg, static_cast(wParam), size); + break; + } + case WM_TIMER: { + OnTimer(static_cast(wParam)); + break; + } + case WM_WINDOWPOSCHANGING: { + LPWINDOWPOS window_pos = reinterpret_cast(lParam); + SnapToEdges(window_pos); + OnWindowPosChanging(window_pos); + break; + } + default: { + if (uMsg == WM_TASKBARCREATED || + uMsg == WM_TASKBARBUTTONCREATED || + uMsg == WM_TASKBARCALLBACK) { + OnTaskbarCallback(uMsg, lParam); + return FALSE; + } + break; + } + } + + return FALSE; +} + +} // namespace win \ No newline at end of file diff --git a/src/win/win_dialog.h b/src/win/win_dialog.h new file mode 100644 index 000000000..5bcc5784b --- /dev/null +++ b/src/win/win_dialog.h @@ -0,0 +1,80 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_WIN_DIALOG_H +#define TAIGA_WIN_DIALOG_H + +#include "win_main.h" +#include "win_window.h" + +namespace win { + +class Dialog : public Window { +public: + Dialog(); + virtual ~Dialog(); + + virtual INT_PTR Create(UINT resource_id, HWND parent = nullptr, bool modal = true); + virtual void EndDialog(INT_PTR result); + virtual void SetSizeMax(LONG cx, LONG cy); + virtual void SetSizeMin(LONG cx, LONG cy); + virtual void SetSnapGap(int snap_gap); + + virtual BOOL AddComboString(int id_combo, LPCWSTR text); + virtual BOOL CheckDlgButton(int id_button, UINT check); + virtual BOOL CheckRadioButton(int id_first_button, int id_last_button, int id_check_button); + virtual BOOL EnableDlgItem(int id_item, BOOL enable); + virtual INT GetCheckedRadioButton(int id_first_button, int id_last_button); + virtual INT GetComboSelection(int id_item); + virtual HWND GetDlgItem(int id_item); + virtual UINT GetDlgItemInt(int id_item); + virtual void GetDlgItemText(int id_item, LPWSTR output, int max_length = MAX_PATH); + virtual void GetDlgItemText(int id_item, std::wstring& output); + virtual std::wstring GetDlgItemText(int id_item); + virtual BOOL HideDlgItem(int id_item); + virtual BOOL IsDlgButtonChecked(int id_button); + virtual BOOL SendDlgItemMessage(int id_item, UINT uMsg, WPARAM wParam, LPARAM lParam); + virtual BOOL SetComboSelection(int id_item, int index); + virtual BOOL SetDlgItemText(int id_item, LPCWSTR text); + virtual BOOL ShowDlgItem(int id_item, int cmd_show = SW_SHOWNORMAL); + +protected: + virtual void OnCancel(); + virtual BOOL OnClose(); + virtual BOOL OnInitDialog(); + virtual void OnOK(); + virtual void RegisterDlgClass(LPCWSTR class_name); + + virtual INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + virtual INT_PTR DialogProcDefault(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +private: + static INT_PTR CALLBACK DialogProcStatic(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + + void SetMinMaxInfo(LPMINMAXINFO mmi); + void SnapToEdges(LPWINDOWPOS window_pos); + + bool modal_; + int snap_gap_; + SIZE size_last_, size_max_, size_min_; + POINT pos_last_; +}; + +} // namespace win + +#endif // TAIGA_WIN_DIALOG_H \ No newline at end of file diff --git a/src/win/win_gdi.cpp b/src/win/win_gdi.cpp new file mode 100644 index 000000000..2238a83c4 --- /dev/null +++ b/src/win/win_gdi.cpp @@ -0,0 +1,368 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_gdi.h" + +namespace win { + +Dc::Dc() + : dc_(nullptr), + bitmap_old_(nullptr), + brush_old_(nullptr), + font_old_(nullptr) { +} + +Dc::Dc(HDC hdc) + : dc_(hdc), + bitmap_old_(nullptr), + brush_old_(nullptr), + font_old_(nullptr) { +} + +Dc::~Dc() { + if (dc_) { + if (bitmap_old_) + ::DeleteObject(::SelectObject(dc_, bitmap_old_)); + if (brush_old_) + ::DeleteObject(::SelectObject(dc_, brush_old_)); + if (font_old_) + ::DeleteObject(::SelectObject(dc_, font_old_)); + + HWND hwnd = ::WindowFromDC(dc_); + if (hwnd) { + ::ReleaseDC(hwnd, dc_); + } else { + ::DeleteDC(dc_); + } + } +} + +void Dc::operator=(const HDC hdc) { + AttachDc(hdc); +} + +void Dc::AttachDc(HDC hdc) { + if (dc_ || !hdc) + return; + + dc_ = hdc; +} + +HDC Dc::DetachDc() { + if (!dc_) + return nullptr; + + if (bitmap_old_) + ::DeleteObject(::SelectObject(dc_, bitmap_old_)); + if (brush_old_) + ::DeleteObject(::SelectObject(dc_, brush_old_)); + if (font_old_) + ::DeleteObject(::SelectObject(dc_, font_old_)); + + HDC hdc = dc_; + dc_ = nullptr; + + return hdc; +} + +HDC Dc::Get() const { + return dc_; +} + +void Dc::AttachBrush(HBRUSH brush) { + if (!dc_ || !brush) + return; + + if (brush_old_) + ::DeleteObject(::SelectObject(dc_, brush_old_)); + + brush_old_ = reinterpret_cast(::SelectObject(dc_, brush)); +} + +void Dc::CreateSolidBrush(COLORREF color) { + if (!dc_) + return; + + if (brush_old_) + ::DeleteObject(::SelectObject(dc_, brush_old_)); + + HBRUSH brush = ::CreateSolidBrush(color); + brush_old_ = reinterpret_cast(::SelectObject(dc_, brush)); +} + +HBRUSH Dc::DetachBrush() { + if (!dc_ || !brush_old_) + return nullptr; + + HBRUSH brush = reinterpret_cast(::SelectObject(dc_, brush_old_)); + brush_old_ = nullptr; + + return brush; +} + +void Dc::AttachFont(HFONT font) { + if (!dc_ || !font) + return; + + if (font_old_) + ::DeleteObject(::SelectObject(dc_, font_old_)); + + font_old_ = reinterpret_cast(::SelectObject(dc_, font)); +} + +HFONT Dc::DetachFont() { + if (!dc_ || !font_old_) + return nullptr; + + HFONT font = reinterpret_cast(::SelectObject(dc_, font_old_)); + font_old_ = nullptr; + + return font; +} + +void Dc::EditFont(LPCWSTR face_name, INT size, + BOOL bold, BOOL italic, BOOL underline) { + if (font_old_) + ::DeleteObject(::SelectObject(dc_, font_old_)); + font_old_ = reinterpret_cast(::GetCurrentObject(dc_, OBJ_FONT)); + + LOGFONT logfont; + ::GetObject(font_old_, sizeof(LOGFONT), &logfont); + + if (face_name) + ::lstrcpy(logfont.lfFaceName, face_name); + if (size > -1) { + logfont.lfHeight = -MulDiv(size, GetDeviceCaps(dc_, LOGPIXELSY), 72); + logfont.lfWidth = 0; + } + if (bold > -1) + logfont.lfWeight = bold ? FW_BOLD : FW_NORMAL; + if (italic > -1) + logfont.lfItalic = italic; + if (underline > -1) + logfont.lfUnderline = underline; + + HFONT font = ::CreateFontIndirect(&logfont); + ::SelectObject(dc_, font); +} + +BOOL Dc::FillRect(const RECT& rect, HBRUSH brush) const { + return ::FillRect(dc_, &rect, brush); +} + +void Dc::FillRect(const RECT& rect, COLORREF color) const { + COLORREF old_color = ::SetBkColor(dc_, color); + ::ExtTextOut(dc_, 0, 0, ETO_OPAQUE, &rect, L"", 0, nullptr); + ::SetBkColor(dc_, old_color); +} + +void Dc::AttachBitmap(HBITMAP bitmap) { + if (!dc_ || !bitmap) + return; + + if (bitmap_old_) + ::DeleteObject(::SelectObject(dc_, bitmap_old_)); + + bitmap_old_ = reinterpret_cast(::SelectObject(dc_, bitmap)); +} + +HBITMAP Dc::DetachBitmap() { + if (!dc_ || !bitmap_old_) + return nullptr; + + HBITMAP bitmap = reinterpret_cast(::SelectObject(dc_, bitmap_old_)); + bitmap_old_ = nullptr; + + return bitmap; +} + +BOOL Dc::BitBlt(int x, int y, int width, int height, + HDC dc_src, int x_src, int y_src, DWORD rop) const { + return ::BitBlt(dc_, x, y, width, height, dc_src, x_src, y_src, rop); +} + +int Dc::SetStretchBltMode(int mode) { + return ::SetStretchBltMode(dc_, mode); +} + +BOOL Dc::StretchBlt(int x, int y, int width, int height, + HDC dc_src, int x_src, int y_src, + int width_src, int height_src, DWORD rop) const { + return ::StretchBlt(dc_, x, y, width, height, + dc_src, x_src, y_src, width_src, height_src, rop); +} + +int Dc::DrawText(LPCWSTR text, int count, const RECT& rect, UINT format) const { + return ::DrawText(dc_, text, count, const_cast(&rect), format); +} + +COLORREF Dc::GetTextColor() const { + return ::GetTextColor(dc_); +} + +COLORREF Dc::SetBkColor(COLORREF color) const { + return ::SetBkColor(dc_, color); +} + +int Dc::SetBkMode(int bk_mode) const { + return ::SetBkMode(dc_, bk_mode); +} + +COLORREF Dc::SetTextColor(COLORREF color) const { + return ::SetTextColor(dc_, color); +} + +//////////////////////////////////////////////////////////////////////////////// + +Brush::Brush() + : brush_(nullptr) { +} + +Brush::Brush(HBRUSH brush) + : brush_(brush) { +} + +Brush::~Brush() { + Set(nullptr); +} + +HBRUSH Brush::Get() const { + return brush_; +} + +void Brush::Set(HBRUSH brush) { + if (brush_) + ::DeleteObject(brush_); + brush_ = brush; +} + +Brush::operator HBRUSH() const { + return brush_; +} + +//////////////////////////////////////////////////////////////////////////////// + +Font::Font() + : font_(nullptr) { +} + +Font::Font(HFONT font) + : font_(font) { +} + +Font::~Font() { + Set(nullptr); +} + +HFONT Font::Get() const { + return font_; +} + +void Font::Set(HFONT font) { + if (font_) + ::DeleteObject(font_); + font_ = font; +} + +Font::operator HFONT() const { + return font_; +} + +//////////////////////////////////////////////////////////////////////////////// + +Rect::Rect() { + left = 0; top = 0; right = 0; bottom = 0; +} + +Rect::Rect(int l, int t, int r, int b) { + left = l; top = t; right = r; bottom = b; +} + +Rect::Rect(const RECT& rect) { + ::CopyRect(this, &rect); +} + +Rect::Rect(LPCRECT rect) { + ::CopyRect(this, rect); +} + +BOOL Rect::operator==(const RECT& rect) const { + return ::EqualRect(this, &rect); +} + +BOOL Rect::operator!=(const RECT& rect) const { + return !::EqualRect(this, &rect); +} + +void Rect::operator=(const RECT& rect) { + ::CopyRect(this, &rect); +} + +int Rect::Height() const { + return bottom - top; +} + +int Rect::Width() const { + return right - left; +} + +void Rect::Copy(const RECT& rect) { + ::CopyRect(this, &rect); +} + +BOOL Rect::Equal(const RECT& rect) const { + return ::EqualRect(&rect, this); +} + +BOOL Rect::Inflate(int dx, int dy) { + return ::InflateRect(this, dx, dy); +} + +BOOL Rect::Intersect(const RECT& rect1, const RECT& rect2) { + return ::IntersectRect(this, &rect1, &rect2); +} + +BOOL Rect::IsEmpty() const { + return ::IsRectEmpty(this); +} + +BOOL Rect::Offset(int dx, int dy) { + return ::OffsetRect(this, dx, dy); +} + +BOOL Rect::PtIn(POINT point) const { + return ::PtInRect(this, point); +} + +BOOL Rect::Set(int left, int top, int right, int bottom) { + return ::SetRect(this, left, top, right, bottom); +} + +BOOL Rect::SetEmpty() { + return ::SetRectEmpty(this); +} + +BOOL Rect::Subtract(const RECT& rect1, const RECT& rect2) { + return ::SubtractRect(this, &rect1, &rect2); +} + +BOOL Rect::Union(const RECT& rect1, const RECT& rect2) { + return ::UnionRect(this, &rect1, &rect2); +} + +} // namespace win \ No newline at end of file diff --git a/src/win/win_gdi.h b/src/win/win_gdi.h new file mode 100644 index 000000000..a373d646c --- /dev/null +++ b/src/win/win_gdi.h @@ -0,0 +1,138 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_WIN_GDI_H +#define TAIGA_WIN_GDI_H + +#include "win_main.h" + +namespace win { + +class Dc { +public: + Dc(); + Dc(HDC hdc); + virtual ~Dc(); + + void operator=(const HDC hdc); + + void AttachDc(HDC hdc); + HDC DetachDc(); + HDC Get() const; + + // Brush + void AttachBrush(HBRUSH brush); + void CreateSolidBrush(COLORREF color); + HBRUSH DetachBrush(); + + // Font + void AttachFont(HFONT font); + HFONT DetachFont(); + void EditFont(LPCWSTR face_name = nullptr, INT size = -1, BOOL bold = -1, BOOL italic = -1, BOOL underline = -1); + + // Painting + BOOL FillRect(const RECT& rect, HBRUSH brush) const; + void FillRect(const RECT& rect, COLORREF color) const; + + // Bitmap + void AttachBitmap(HBITMAP bitmap); + BOOL BitBlt(int x, int y, int width, int height, HDC dc_src, int x_src, int y_src, DWORD rop) const; + HBITMAP DetachBitmap(); + int SetStretchBltMode(int mode); + BOOL StretchBlt(int x, int y, int width, int height, HDC dc_src, int x_src, int y_src, int width_src, int height_src, DWORD rop) const; + + // Text + int DrawText(LPCWSTR text, int count, const RECT& rect, UINT format) const; + COLORREF GetTextColor() const; + COLORREF SetBkColor(COLORREF color) const; + int SetBkMode(int bk_mode) const; + COLORREF SetTextColor(COLORREF color) const; + +private: + HDC dc_; + HBITMAP bitmap_old_; + HBRUSH brush_old_; + HFONT font_old_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class Brush { +public: + Brush(); + Brush(HBRUSH brush); + ~Brush(); + + HBRUSH Get() const; + void Set(HBRUSH brush); + + operator HBRUSH() const; + +private: + HBRUSH brush_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class Font { +public: + Font(); + Font(HFONT font); + ~Font(); + + HFONT Get() const; + void Set(HFONT font); + + operator HFONT() const; + +private: + HFONT font_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class Rect : public RECT { +public: + Rect(); + Rect(int l, int t, int r, int b); + Rect(const RECT& rect); + Rect(LPCRECT rect); + + BOOL operator==(const RECT& rect) const; + BOOL operator!=(const RECT& rect) const; + void operator=(const RECT& rect); + + int Height() const; + int Width() const; + + void Copy(const RECT& rect); + BOOL Equal(const RECT& rect) const; + BOOL Inflate(int dx, int dy); + BOOL Intersect(const RECT& rect1, const RECT& rect2); + BOOL IsEmpty() const; + BOOL Offset(int dx, int dy); + BOOL PtIn(POINT point) const; + BOOL Set(int left, int top, int right, int bottom); + BOOL SetEmpty(); + BOOL Subtract(const RECT& rect1, const RECT& rect2); + BOOL Union(const RECT& rect1, const RECT& rect2); +}; + +} // namespace win + +#endif // TAIGA_WIN_GDI_H \ No newline at end of file diff --git a/src/win/win_gdiplus.cpp b/src/win/win_gdiplus.cpp new file mode 100644 index 000000000..87832f8c1 --- /dev/null +++ b/src/win/win_gdiplus.cpp @@ -0,0 +1,72 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_gdiplus.h" +#include + +#pragma comment(lib, "gdiplus.lib") + +namespace win { + +GdiPlus::GdiPlus() + : token_(0) { + Gdiplus::GdiplusStartupInput input; + Gdiplus::GdiplusStartup(&token_, &input, nullptr); +} + +GdiPlus::~GdiPlus() { + Gdiplus::GdiplusShutdown(token_); + token_ = 0; +} + +void GdiPlus::DrawRectangle(const HDC hdc, const RECT& rect, + Gdiplus::ARGB color) { + const Gdiplus::SolidBrush brush = Gdiplus::Color(color); + Gdiplus::Graphics graphics(hdc); + graphics.FillRectangle(&brush, rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top); +} + +HICON GdiPlus::LoadIcon(const std::wstring& file) { + HICON icon = nullptr; + + Gdiplus::Bitmap* gp_bitmap = Gdiplus::Bitmap::FromFile(file.c_str()); + if (gp_bitmap) { + gp_bitmap->GetHICON(&icon); + delete gp_bitmap; + gp_bitmap = nullptr; + } + + return icon; +} + +HBITMAP GdiPlus::LoadImage(const std::wstring& file) { + HBITMAP bitmap = nullptr; + + Gdiplus::Bitmap* gp_bitmap = Gdiplus::Bitmap::FromFile(file.c_str()); + if (gp_bitmap) { + Gdiplus::Color color(Gdiplus::Color::AlphaMask); + gp_bitmap->GetHBITMAP(color, &bitmap); + delete gp_bitmap; + gp_bitmap = nullptr; + } + + return bitmap; +} + +} // namespace win \ No newline at end of file diff --git a/win32/win_gdiplus.h b/src/win/win_gdiplus.h similarity index 66% rename from win32/win_gdiplus.h rename to src/win/win_gdiplus.h index bb1876734..c08326d7d 100644 --- a/win32/win_gdiplus.h +++ b/src/win/win_gdiplus.h @@ -1,43 +1,41 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef WIN_GDIPLUS_H -#define WIN_GDIPLUS_H - -#include "win_main.h" - -namespace win32 { - -// ============================================================================= - -class GdiPlus { -public: - GdiPlus(); - ~GdiPlus(); - - void DrawRectangle(const HDC hdc, const RECT& rect, DWORD color); - HICON LoadIcon(const wstring& file); - HBITMAP LoadImage(const wstring& file); - -private: - ULONG_PTR m_Token; -}; - -} // namespace win32 - -#endif // WIN_GDIPLUS_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_WIN_GDIPLUS_H +#define TAIGA_WIN_GDIPLUS_H + +#include "win_main.h" + +namespace win { + +class GdiPlus { +public: + GdiPlus(); + ~GdiPlus(); + + void DrawRectangle(const HDC hdc, const RECT& rect, DWORD color); + HICON LoadIcon(const std::wstring& file); + HBITMAP LoadImage(const std::wstring& file); + +private: + ULONG_PTR token_; +}; + +} // namespace win + +#endif // TAIGA_WIN_GDIPLUS_H \ No newline at end of file diff --git a/src/win/win_main.cpp b/src/win/win_main.cpp new file mode 100644 index 000000000..fbc3f2be7 --- /dev/null +++ b/src/win/win_main.cpp @@ -0,0 +1,219 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_main.h" +#include "win_window.h" + +namespace win { + +class WindowMap WindowMap; + +void WindowMap::Add(HWND hwnd, Window* window) { + if (hwnd != nullptr) { + if (!GetWindow(hwnd)) { + window_map_.insert(std::make_pair(hwnd, window)); + } + } +} + +void WindowMap::Clear() { + if (window_map_.empty()) + return; + + for (auto it = window_map_.begin(); it != window_map_.end(); ++it) { + HWND hwnd = it->first; + if (::IsWindow(hwnd)) + ::DestroyWindow(hwnd); + } + + window_map_.clear(); +} + +Window* WindowMap::GetWindow(HWND hwnd) { + if (window_map_.empty()) + return nullptr; + + auto it = window_map_.find(hwnd); + if (it != window_map_.end()) + return it->second; + + return nullptr; +} + +void WindowMap::Remove(HWND hwnd) { + if (window_map_.empty()) + return; + + for (auto it = window_map_.begin(); it != window_map_.end(); ++it) { + if (hwnd == it->first) { + window_map_.erase(it); + return; + } + } +} + +void WindowMap::Remove(Window* window) { + if (window_map_.empty()) + return; + + for (auto it = window_map_.begin(); it != window_map_.end(); ++it) { + if (window == it->second) { + window_map_.erase(it); + return; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +App::App() { + instance_ = ::GetModuleHandle(nullptr); +} + +App::~App() { + WindowMap.Clear(); +} + +BOOL App::InitCommonControls(DWORD flags) const { + INITCOMMONCONTROLSEX icc; + icc.dwSize = sizeof(INITCOMMONCONTROLSEX); + icc.dwICC = flags; + + return ::InitCommonControlsEx(&icc); +} + +BOOL App::InitInstance() { + return TRUE; +} + +int App::MessageLoop() { + MSG msg; + + while (::GetMessage(&msg, nullptr, 0, 0)) { + BOOL processed = FALSE; + if ((msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST) || + (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST)) { + for (HWND hwnd = msg.hwnd; hwnd != nullptr; hwnd = ::GetParent(hwnd)) { + auto window = WindowMap.GetWindow(hwnd); + if (window) { + processed = window->PreTranslateMessage(&msg); + if (processed) + break; + } + } + } + + if (!processed) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + } + + return static_cast(LOWORD(msg.wParam)); +} + +void App::PostQuitMessage(int exit_code) { + ::PostQuitMessage(exit_code); +} + +int App::Run() { + if (InitInstance()) { + return MessageLoop(); + } else { + ::PostQuitMessage(-1); + return -1; + } +} + +std::wstring App::GetCurrentDirectory() const { + WCHAR path[MAX_PATH]; + DWORD len = ::GetCurrentDirectory(MAX_PATH, path); + if (len > 0 && len < MAX_PATH) + return path; + + return std::wstring(); +} + +HINSTANCE App::GetInstanceHandle() const { + return instance_; +} + +std::wstring App::GetModulePath() const { + WCHAR path[MAX_PATH]; + ::GetModuleFileName(instance_, path, MAX_PATH); + return path; +} + +BOOL App::SetCurrentDirectory(const std::wstring& directory) { + return ::SetCurrentDirectory(directory.c_str()); +} + +//////////////////////////////////////////////////////////////////////////////// + +Version GetVersion() { + static bool checked = false; + static Version version = kVersionPreXp; + + if (!checked) { + OSVERSIONINFOEX version_info; + version_info.dwOSVersionInfoSize = sizeof(version_info); + GetVersionEx(reinterpret_cast(&version_info)); + + if (version_info.dwMajorVersion == 5) { + switch (version_info.dwMinorVersion) { + case 0: + version = kVersionPreXp; + break; + case 1: + version = kVersionXp; + break; + case 2: + default: + version = kVersionServer2003; + break; + } + } else if (version_info.dwMajorVersion == 6) { + if (version_info.wProductType != VER_NT_WORKSTATION) { + version = kVersionServer2008; + } else { + switch (version_info.dwMinorVersion) { + case 0: + version = kVersionVista; + break; + case 1: + version = kVersion7; + break; + case 2: + version = kVersion8; + break; + default: + version = kVersion8_1; + break; + } + } + } else if (version_info.dwMajorVersion > 6) { + version = kVersionUnknown; + } + + checked = true; + } + + return version; +} + +} // namespace win \ No newline at end of file diff --git a/src/win/win_main.h b/src/win/win_main.h new file mode 100644 index 000000000..c5d79a55c --- /dev/null +++ b/src/win/win_main.h @@ -0,0 +1,88 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_WIN_MAIN_H +#define TAIGA_WIN_MAIN_H + +#include +#include +#include + +#include +#include +#include + +namespace win { + +class Window; + +class WindowMap { +public: + void Add(HWND hwnd, Window* window); + void Clear(); + Window* GetWindow(HWND hwnd); + void Remove(HWND hwnd); + void Remove(Window* window); + +private: + std::map window_map_; +}; + +extern class WindowMap WindowMap; + +//////////////////////////////////////////////////////////////////////////////// + +class App { +public: + App(); + virtual ~App(); + + BOOL InitCommonControls(DWORD flags) const; + virtual BOOL InitInstance(); + virtual int MessageLoop(); + virtual void PostQuitMessage(int exit_code = 0); + virtual int Run(); + + std::wstring GetCurrentDirectory() const; + HINSTANCE GetInstanceHandle() const; + std::wstring GetModulePath() const; + BOOL SetCurrentDirectory(const std::wstring& directory); + +private: + HINSTANCE instance_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +enum Version { + kVersionPreXp = 0, + kVersionXp, + kVersionServer2003, + kVersionVista, + kVersionServer2008, + kVersion7, + kVersion8, + kVersion8_1, + kVersionUnknown +}; + +Version GetVersion(); + +} // namespace win + +#endif // TAIGA_WIN_MAIN_H \ No newline at end of file diff --git a/src/win/win_menu.cpp b/src/win/win_menu.cpp new file mode 100644 index 000000000..256cb2137 --- /dev/null +++ b/src/win/win_menu.cpp @@ -0,0 +1,153 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_menu.h" + +namespace win { + +HMENU MenuList::CreateNewMenu(LPCWSTR name, std::vector& menu_handles) { + auto menu = FindMenu(name); + + if (!menu) + return nullptr; + + HMENU handle = nullptr; + if (menu->type == L"menubar") { + handle = ::CreateMenu(); + } else { + handle = ::CreatePopupMenu(); + } + + if (!handle) + return nullptr; + + menu_handles.push_back(handle); + + for (auto item = menu->items.begin(); item != menu->items.end(); ++item) { + if (!item->visible) + continue; + const UINT flags = + (item->checked ? MF_CHECKED : 0) | + (item->def ? MF_DEFAULT : 0) | + (item->enabled ? MF_ENABLED : MF_GRAYED) | + (item->new_column ? MF_MENUBARBREAK : 0) | + (item->radio ? MFT_RADIOCHECK : 0); + switch (item->type) { + case kMenuItemDefault: { + UINT_PTR id_new_item = reinterpret_cast(&item->action); + ::AppendMenu(handle, MF_STRING | flags, id_new_item, + item->name.c_str()); + if (item->def) + ::SetMenuDefaultItem(handle, id_new_item, FALSE); + break; + } + case kMenuItemSeparator: { + ::AppendMenu(handle, MF_SEPARATOR, 0, nullptr); + break; + } + case kMenuItemSubmenu: { + auto submenu = FindMenu(item->submenu.c_str()); + if (submenu && submenu != menu) { + HMENU submenu_handle = CreateNewMenu(submenu->name.c_str(), + menu_handles); + ::AppendMenu(handle, MF_POPUP | flags, + reinterpret_cast(submenu_handle), + item->name.c_str()); + } + break; + } + } + } + + return handle; +} + +std::wstring MenuList::Show(HWND hwnd, int x, int y, LPCWSTR name) { + std::vector menu_handles; + CreateNewMenu(name, menu_handles); + + if (menu_handles.empty()) + return std::wstring(); + + if (!x && !y) { + POINT point; + ::GetCursorPos(&point); + x = point.x; + y = point.y; + } + + UINT flags = TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD; + UINT_PTR index = ::TrackPopupMenuEx(menu_handles.front(), + flags, x, y, hwnd, nullptr); + + for (auto it = menu_handles.begin(); it != menu_handles.end(); ++it) + ::DestroyMenu(*it); + + if (index > 0) { + auto str = reinterpret_cast(index); + return *str; + } else { + return std::wstring(); + } +} + +void MenuList::Create(LPCWSTR name, LPCWSTR type) { + Menu menu; + + menu.name = name; + menu.type = type; + + menus.push_back(menu); +} + +Menu* MenuList::FindMenu(LPCWSTR name) { + for (auto it = menus.begin(); it != menus.end(); ++it) + if (it->name == name) + return &(*it); + + return nullptr; +} + +void Menu::CreateItem(std::wstring action, std::wstring name, + std::wstring submenu, + bool checked, bool def, bool enabled, + bool newcolumn, bool radio) { + MenuItem item; + + item.action = action; + item.checked = checked; + item.def = def; + item.enabled = enabled; + item.name = name; + item.new_column = newcolumn; + item.radio = radio; + item.submenu = submenu; + item.visible = true; + + if (!submenu.empty()) { + item.type = kMenuItemSubmenu; + } else if (name.empty()) { + item.type = kMenuItemSeparator; + } else { + item.type = kMenuItemDefault; + } + + items.push_back(item); +} + +} // namespace win \ No newline at end of file diff --git a/src/win/win_menu.h b/src/win/win_menu.h new file mode 100644 index 000000000..60c5340ea --- /dev/null +++ b/src/win/win_menu.h @@ -0,0 +1,67 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_WIN_MENU_H +#define TAIGA_WIN_MENU_H + +#include "win_main.h" + +namespace win { + +enum MenuItemType { + kMenuItemDefault = 0, + kMenuItemSeparator, + kMenuItemSubmenu +}; + +class MenuItem { +public: + bool checked, def, enabled, new_column, radio, visible; + std::wstring action, name, submenu; + int type; +}; + +class Menu { +public: + void CreateItem(std::wstring action = L"", + std::wstring name = L"", + std::wstring submenu = L"", + bool checked = false, + bool def = false, + bool enabled = true, + bool newcolumn = false, + bool radio = false); + + std::vector items; + std::wstring name; + std::wstring type; +}; + +class MenuList { +public: + void Create(LPCWSTR name, LPCWSTR type); + HMENU CreateNewMenu(LPCWSTR name, std::vector& menu_handles); + Menu* FindMenu(LPCWSTR name); + std::wstring Show(HWND hwnd, int x, int y, LPCWSTR name); + + std::vector menus; +}; + +} // namespace win + +#endif // TAIGA_WIN_MENU_H \ No newline at end of file diff --git a/src/win/win_registry.cpp b/src/win/win_registry.cpp new file mode 100644 index 000000000..df7199a2e --- /dev/null +++ b/src/win/win_registry.cpp @@ -0,0 +1,157 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_registry.h" + +namespace win { + +Registry::Registry() + : key_(nullptr) { +} + +Registry::~Registry() { + CloseKey(); +} + +LSTATUS Registry::CloseKey() { + if (key_) { + LSTATUS status = ::RegCloseKey(key_); + key_ = nullptr; + return status; + } else { + return ERROR_SUCCESS; + } +} + +LSTATUS Registry::CreateKey(HKEY key, + const std::wstring& subkey, + LPWSTR class_type, + DWORD options, + REGSAM sam_desired, + LPSECURITY_ATTRIBUTES security_attributes, + LPDWORD disposition) { + CloseKey(); + + return ::RegCreateKeyEx( + key, subkey.c_str(), 0, class_type, options, sam_desired, + security_attributes, &key_, disposition); +} + +LONG Registry::DeleteKey(const std::wstring& subkey) { + return DeleteSubkeys(key_, subkey); +} + +LSTATUS Registry::DeleteSubkeys(HKEY root_key, const std::wstring& subkey) { + LSTATUS status = ::RegDeleteKey(root_key, subkey.c_str()); + + if (status == ERROR_SUCCESS) + return status; + + HKEY key; + status = ::RegOpenKeyEx(root_key, subkey.c_str(), 0, KEY_READ, &key); + + if (status != ERROR_SUCCESS) + return status; + + DWORD subkey_count = 0; + std::vector keys; + WCHAR name[MAX_PATH] = {L'\0'}; + + if (::RegQueryInfoKey(key, nullptr, nullptr, nullptr, &subkey_count, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr) == ERROR_SUCCESS) { + for (DWORD i = 0; i < subkey_count; i++) { + DWORD name_length = MAX_PATH; + if (::RegEnumKeyEx(key, i, name, &name_length, nullptr, nullptr, nullptr, + nullptr) == ERROR_SUCCESS) { + keys.push_back(name); + } + } + } + + for (auto it = keys.begin(); it != keys.end(); ++it) { + DeleteSubkeys(key, it->c_str()); + } + + ::RegCloseKey(key); + + return ::RegDeleteKey(root_key, subkey.c_str()); +} + +LSTATUS Registry::DeleteValue(const std::wstring& value_name) { + return ::RegDeleteValue(key_, value_name.c_str()); +} + +void Registry::EnumKeys(std::vector& output) { + // sam_desired must be: KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS + + WCHAR name[MAX_PATH] = {0}; + DWORD subkey_count = 0; + + if (::RegQueryInfoKey(key_, nullptr, nullptr, nullptr, &subkey_count, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr) == ERROR_SUCCESS) { + for (DWORD i = 0; i < subkey_count; i++) { + DWORD name_length = MAX_PATH; + if (::RegEnumKeyEx(key_, i, name, &name_length, nullptr, nullptr, nullptr, + nullptr) == ERROR_SUCCESS) { + output.push_back(name); + } + } + } +} + +LSTATUS Registry::OpenKey(HKEY key, const std::wstring& subkey, DWORD options, + REGSAM sam_desired) { + CloseKey(); + + return ::RegOpenKeyEx(key, subkey.c_str(), options, sam_desired, &key_); +} + +LSTATUS Registry::QueryValue(const std::wstring& value_name, LPDWORD type, + LPBYTE data, LPDWORD data_size) { + return ::RegQueryValueEx(key_, value_name.c_str(), nullptr, type, + data, data_size); +} + +LSTATUS Registry::SetValue(const std::wstring& value_name, DWORD type, + CONST BYTE* data, DWORD data_size) { + return ::RegSetValueEx(key_, value_name.c_str(), 0, type, + data, data_size); +} + +std::wstring Registry::QueryValue(const std::wstring& value_name) { + DWORD type = 0; + WCHAR buffer[MAX_PATH]; + DWORD buffer_size = sizeof(buffer); + + if (QueryValue(value_name.c_str(), &type, reinterpret_cast(&buffer), + &buffer_size) == ERROR_SUCCESS) + if (type == REG_SZ) + return buffer; + + return std::wstring(); +} + +void Registry::SetValue(const std::wstring& value_name, + const std::wstring& value) { + SetValue(value_name.c_str(), REG_SZ, (LPBYTE)value.c_str(), + value.length() * sizeof(WCHAR)); +} + +} // namespace win \ No newline at end of file diff --git a/src/win/win_registry.h b/src/win/win_registry.h new file mode 100644 index 000000000..be3e31fa4 --- /dev/null +++ b/src/win/win_registry.h @@ -0,0 +1,71 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_WIN_REGISTRY_H +#define TAIGA_WIN_REGISTRY_H + +#include "win_main.h" + +namespace win { + +class Registry { +public: + Registry(); + ~Registry(); + + LSTATUS CloseKey(); + LSTATUS CreateKey( + HKEY key, + const std::wstring& subkey, + LPWSTR class_type = nullptr, + DWORD options = REG_OPTION_NON_VOLATILE, + REGSAM sam_desired = KEY_SET_VALUE, + LPSECURITY_ATTRIBUTES security_attributes = nullptr, + LPDWORD disposition = nullptr); + LONG DeleteKey(const std::wstring& subkey); + LSTATUS DeleteValue(const std::wstring& value_name); + void EnumKeys(std::vector& output); + LSTATUS OpenKey( + HKEY key, + const std::wstring& subkey, + DWORD options = 0, + REGSAM sam_desired = KEY_QUERY_VALUE); + LSTATUS QueryValue( + const std::wstring& value_name, + LPDWORD type, + LPBYTE data, + LPDWORD data_size); + std::wstring QueryValue(const std::wstring& value_name); + LSTATUS SetValue( + const std::wstring& value_name, + DWORD type, + CONST BYTE* data, + DWORD data_size); + void SetValue( + const std::wstring& value_name, + const std::wstring& value); + +private: + LSTATUS DeleteSubkeys(HKEY root_key, const std::wstring& subkey); + + HKEY key_; +}; + +} // namespace win + +#endif // TAIGA_WIN_REGISTRY_H \ No newline at end of file diff --git a/win32/win_resizable.cpp b/src/win/win_resizable.cpp similarity index 68% rename from win32/win_resizable.cpp rename to src/win/win_resizable.cpp index bc491acfa..75739f8f7 100644 --- a/win32/win_resizable.cpp +++ b/src/win/win_resizable.cpp @@ -1,160 +1,156 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_main.h" -#include "win_resizable.h" -#include "win_gdi.h" - -// see: http://support.microsoft.com/kb/262954 - -namespace win32 { - -// ============================================================================= - -Resizable::Resizable() : - x_(1), y_(1) { -} - -void Resizable::ResizeProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_INITDIALOG: - OnInitDialog(hwnd); - break; - case WM_HSCROLL: - OnScroll(hwnd, SB_HORZ, LOWORD(wParam)); - break; - case WM_VSCROLL: - OnScroll(hwnd, SB_VERT, LOWORD(wParam)); - break; - case WM_SIZE: { - SIZE size = {LOWORD(lParam), HIWORD(lParam)}; - OnSize(hwnd, static_cast(wParam), size); - break; - } - } -} - -void Resizable::OnInitDialog(HWND hwnd) { - Rect rect; - ::GetClientRect(hwnd, &rect); - - SCROLLINFO si = {0}; - si.cbSize = sizeof(SCROLLINFO); - si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE; - si.nMin = 1; - si.nPos = 1; - - si.nMax = rect.Width(); - si.nPage = rect.Width(); - ::SetScrollInfo(hwnd, SB_HORZ, &si, FALSE); - - si.nMax = rect.Height(); - si.nPage = rect.Height(); - ::SetScrollInfo(hwnd, SB_VERT, &si, FALSE); -} - -void Resizable::OnScroll(HWND hwnd, int nBar, UINT code) { - SCROLLINFO si = {0}; - si.cbSize = sizeof(SCROLLINFO); - si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS; - ::GetScrollInfo(hwnd, nBar, &si); - - int nMin = si.nMin; - int nMax = si.nMax - (si.nPage - 1); - int pos = -1; - - switch (code) { - case SB_LINEUP: - pos = max(si.nPos - 1, nMin); - break; - case SB_LINEDOWN: - pos = min(si.nPos + 1, nMax); - break; - case SB_PAGEUP: - pos = max(si.nPos - (int)si.nPage, nMin); - break; - case SB_PAGEDOWN: - pos = min(si.nPos + (int)si.nPage, nMax); - break; - case SB_THUMBPOSITION: - break; - case SB_THUMBTRACK: - pos = si.nTrackPos; - break; - case SB_TOP: - pos = nMin; - break; - case SB_BOTTOM: - pos = nMax; - break; - case SB_ENDSCROLL: - break; - default: - return; - } - - if (pos == -1) - return; - - ::SetScrollPos(hwnd, nBar, pos, TRUE); - ScrollClient(hwnd, nBar, pos); -} - -void Resizable::OnSize(HWND hwnd, UINT nType, SIZE size) { - if (nType != SIZE_RESTORED && nType != SIZE_MAXIMIZED) - return; - - SCROLLINFO si = {0}; - si.cbSize = sizeof(SCROLLINFO); - - const int bar[] = { SB_HORZ, SB_VERT }; - const int page[] = { size.cx, size.cy }; - - for (size_t i = 0; i < ARRAYSIZE(bar); i++) { - si.fMask = SIF_PAGE; - si.nPage = page[i]; - ::SetScrollInfo(hwnd, bar[i], &si, TRUE); - - si.fMask = SIF_RANGE | SIF_POS; - ::GetScrollInfo(hwnd, bar[i], &si); - - const int maxScrollPos = si.nMax - (page[i] - 1); - - if ((si.nPos != si.nMin && si.nPos == maxScrollPos) || - (nType == SIZE_MAXIMIZED)) { - ScrollClient(hwnd, bar[i], si.nPos); - } - } -} - -void Resizable::ScrollClient(HWND hwnd, int nBar, int pos) { - int x = 0; - int y = 0; - - int& delta = nBar == SB_HORZ ? x : y; - int& prev = nBar == SB_HORZ ? x_ : y_; - - delta = prev - pos; - prev = pos; - - if (x || y) { - ::ScrollWindow(hwnd, x, y, nullptr, nullptr); - } -} - -} // namespace win32 \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_main.h" +#include "win_resizable.h" +#include "win_gdi.h" + +// see: http://support.microsoft.com/kb/262954 + +namespace win { + +Resizable::Resizable() + : x_(1), y_(1) { +} + +void Resizable::ResizeProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_INITDIALOG: + OnInitDialog(hwnd); + break; + case WM_HSCROLL: + OnScroll(hwnd, SB_HORZ, LOWORD(wParam)); + break; + case WM_VSCROLL: + OnScroll(hwnd, SB_VERT, LOWORD(wParam)); + break; + case WM_SIZE: { + SIZE size = {LOWORD(lParam), HIWORD(lParam)}; + OnSize(hwnd, static_cast(wParam), size); + break; + } + } +} + +void Resizable::OnInitDialog(HWND hwnd) { + Rect rect; + ::GetClientRect(hwnd, &rect); + + SCROLLINFO si = {0}; + si.cbSize = sizeof(SCROLLINFO); + si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE; + si.nMin = 1; + si.nPos = 1; + + si.nMax = rect.Width(); + si.nPage = rect.Width(); + ::SetScrollInfo(hwnd, SB_HORZ, &si, FALSE); + + si.nMax = rect.Height(); + si.nPage = rect.Height(); + ::SetScrollInfo(hwnd, SB_VERT, &si, FALSE); +} + +void Resizable::OnScroll(HWND hwnd, int bar, UINT code) { + SCROLLINFO si = {0}; + si.cbSize = sizeof(SCROLLINFO); + si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS; + ::GetScrollInfo(hwnd, bar, &si); + + int min_pos = si.nMin; + int max_pos = si.nMax - (si.nPage - 1); + int pos = -1; + + switch (code) { + case SB_LINEUP: + pos = max(si.nPos - 1, min_pos); + break; + case SB_LINEDOWN: + pos = min(si.nPos + 1, max_pos); + break; + case SB_PAGEUP: + pos = max(si.nPos - (int)si.nPage, min_pos); + break; + case SB_PAGEDOWN: + pos = min(si.nPos + (int)si.nPage, max_pos); + break; + case SB_THUMBPOSITION: + break; + case SB_THUMBTRACK: + pos = si.nTrackPos; + break; + case SB_TOP: + pos = min_pos; + break; + case SB_BOTTOM: + pos = max_pos; + break; + case SB_ENDSCROLL: + break; + default: + return; + } + + if (pos == -1) + return; + + ::SetScrollPos(hwnd, bar, pos, TRUE); + ScrollClient(hwnd, bar, pos); +} + +void Resizable::OnSize(HWND hwnd, UINT type, SIZE size) { + if (type != SIZE_RESTORED && type != SIZE_MAXIMIZED) + return; + + SCROLLINFO si = {0}; + si.cbSize = sizeof(SCROLLINFO); + + const int bar[] = { SB_HORZ, SB_VERT }; + const int page[] = { size.cx, size.cy }; + + for (size_t i = 0; i < ARRAYSIZE(bar); i++) { + si.fMask = SIF_PAGE; + si.nPage = page[i]; + ::SetScrollInfo(hwnd, bar[i], &si, TRUE); + + si.fMask = SIF_RANGE | SIF_POS; + ::GetScrollInfo(hwnd, bar[i], &si); + + const int max_pos = si.nMax - (page[i] - 1); + + if ((si.nPos != si.nMin && si.nPos == max_pos) || + (type == SIZE_MAXIMIZED)) + ScrollClient(hwnd, bar[i], si.nPos); + } +} + +void Resizable::ScrollClient(HWND hwnd, int bar, int pos) { + int x = 0; + int y = 0; + + int& delta = bar == SB_HORZ ? x : y; + int& prev = bar == SB_HORZ ? x_ : y_; + + delta = prev - pos; + prev = pos; + + if (x || y) + ::ScrollWindow(hwnd, x, y, nullptr, nullptr); +} + +} // namespace win \ No newline at end of file diff --git a/win32/win_resizable.h b/src/win/win_resizable.h similarity index 65% rename from win32/win_resizable.h rename to src/win/win_resizable.h index 1a073503f..835923196 100644 --- a/win32/win_resizable.h +++ b/src/win/win_resizable.h @@ -1,46 +1,44 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef WIN_RESIZABLE_H -#define WIN_RESIZABLE_H - -#include "win_main.h" - -namespace win32 { - -// ============================================================================= - -class Resizable { -public: - Resizable(); - virtual ~Resizable() {} - - void ResizeProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - -private: - void OnInitDialog(HWND hwnd); - void OnScroll(HWND hwnd, int nBar, UINT code); - void OnSize(HWND hwnd, UINT nType, SIZE size); - void ScrollClient(HWND hwnd, int nBar, int pos); - - int x_, y_; -}; - -} // namespace win32 - -#endif // WIN_RESIZABLE_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_WIN_RESIZABLE_H +#define TAIGA_WIN_RESIZABLE_H + +#include "win_main.h" + +namespace win { + +class Resizable { +public: + Resizable(); + virtual ~Resizable() {} + + void ResizeProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +private: + void OnInitDialog(HWND hwnd); + void OnScroll(HWND hwnd, int bar, UINT code); + void OnSize(HWND hwnd, UINT type, SIZE size); + void ScrollClient(HWND hwnd, int bar, int pos); + + int x_, y_; +}; + +} // namespace win + +#endif // TAIGA_WIN_RESIZABLE_H \ No newline at end of file diff --git a/src/win/win_taskbar.cpp b/src/win/win_taskbar.cpp new file mode 100644 index 000000000..5221e0ef2 --- /dev/null +++ b/src/win/win_taskbar.cpp @@ -0,0 +1,140 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_main.h" +#include "win_taskbar.h" + +class win::Taskbar Taskbar; +class win::TaskbarList TaskbarList; + +const DWORD WM_TASKBARCALLBACK = WM_APP + 0x15; +const DWORD WM_TASKBARCREATED = ::RegisterWindowMessage(L"TaskbarCreated"); +const DWORD WM_TASKBARBUTTONCREATED = ::RegisterWindowMessage(L"TaskbarButtonCreated"); + +namespace win { + +const UINT kAppSysTrayId = 74164; // TAIGA ^_^ + +Taskbar::Taskbar() + : hwnd_(nullptr) { + Version version = GetVersion(); + if (version >= kVersionVista) { + data_.cbSize = sizeof(NOTIFYICONDATA); + } else if (version >= kVersionXp) { + data_.cbSize = NOTIFYICONDATA_V3_SIZE; + } else { + data_.cbSize = NOTIFYICONDATA_V2_SIZE; + } +} + +Taskbar::~Taskbar() { + Destroy(); +} + +BOOL Taskbar::Create(HWND hwnd, HICON icon, LPCWSTR tip) { + Destroy(); + + hwnd_ = hwnd; + + data_.hIcon = icon; + data_.hWnd = hwnd; + data_.szTip[0] = (WCHAR)'\0'; + data_.uCallbackMessage = WM_TASKBARCALLBACK; + data_.uID = kAppSysTrayId; + data_.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + + if (!icon) + data_.hIcon = reinterpret_cast(LoadImage( + GetModuleHandle(nullptr), MAKEINTRESOURCE(101), IMAGE_ICON, + GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), + LR_DEFAULTCOLOR)); + + if (tip) + wcscpy_s(data_.szTip, tip); + + return ::Shell_NotifyIcon(NIM_ADD, &data_); +} + +BOOL Taskbar::Destroy() { + if (!hwnd_) + return FALSE; + + return ::Shell_NotifyIcon(NIM_DELETE, &data_); +} + +BOOL Taskbar::Modify(LPCWSTR tip) { + if (!hwnd_) + return FALSE; + + data_.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; + wcsncpy_s(data_.szTip, 128, tip, _TRUNCATE); + + return ::Shell_NotifyIcon(NIM_MODIFY, &data_); +} + +BOOL Taskbar::Tip(LPCWSTR lpText, LPCWSTR lpTitle, int iIconIndex) { + if (!hwnd_) + return FALSE; + + data_.uFlags = NIF_INFO; + data_.dwInfoFlags = iIconIndex; + wcsncpy_s(data_.szInfo, 256, lpText, _TRUNCATE); + wcsncpy_s(data_.szInfoTitle, 64, lpTitle, _TRUNCATE); + + return ::Shell_NotifyIcon(NIM_MODIFY, &data_); +} + +//////////////////////////////////////////////////////////////////////////////// + +TaskbarList::TaskbarList() + : hwnd_(nullptr), + taskbar_list_(nullptr) { +} + +TaskbarList::~TaskbarList() { + Release(); +} + +void TaskbarList::Initialize(HWND hwnd) { + Release(); + + hwnd_ = hwnd; + + ::CoCreateInstance(CLSID_TaskbarList, nullptr, CLSCTX_INPROC_SERVER, + __uuidof(ITaskbarList3), (void**)&taskbar_list_); +} + +void TaskbarList::Release() { + if (taskbar_list_) { + taskbar_list_->Release(); + taskbar_list_ = nullptr; + hwnd_ = nullptr; + } +} + +void TaskbarList::SetProgressState(TBPFLAG flag) { + if (taskbar_list_) + taskbar_list_->SetProgressState(hwnd_, flag); +} + +void TaskbarList::SetProgressValue(ULONGLONG value, ULONGLONG total) { + if (taskbar_list_) + taskbar_list_->SetProgressValue(hwnd_, value, total); +} + +} // namespace win \ No newline at end of file diff --git a/win32/win_taskbar.h b/src/win/win_taskbar.h similarity index 55% rename from win32/win_taskbar.h rename to src/win/win_taskbar.h index 25a61932f..cbbc67f6e 100644 --- a/win32/win_taskbar.h +++ b/src/win/win_taskbar.h @@ -1,71 +1,69 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef WIN_TASKBAR_H -#define WIN_TASKBAR_H - -#include - -namespace win32 { - -// ============================================================================= - -/* Taskbar */ - -class Taskbar { -public: - Taskbar(); - virtual ~Taskbar(); - - BOOL Create(HWND hwnd, HICON hIcon, LPCWSTR lpTooltip); - BOOL Destroy(); - BOOL Modify(LPCWSTR lpTip); - BOOL Tip(LPCWSTR lpText, LPCWSTR lpTitle, int iIconIndex); - -private: - HWND m_hApp; - NOTIFYICONDATA m_NID; -}; - -/* Taskbar list */ - -class TaskbarList { -public: - TaskbarList(); - virtual ~TaskbarList(); - - void Initialize(HWND hwnd); - void Release(); - void SetProgressState(TBPFLAG flag); - void SetProgressValue(ULONGLONG ullValue, ULONGLONG ullTotal); - -private: - HWND m_hWnd; - ITaskbarList3* m_pTaskbarList; -}; - -} // namespace win32 - -extern class win32::Taskbar Taskbar; -extern class win32::TaskbarList TaskbarList; - -extern const DWORD WM_TASKBARCALLBACK; -extern const DWORD WM_TASKBARCREATED; -extern const DWORD WM_TASKBARBUTTONCREATED; - -#endif // WIN_TASKBAR_H \ No newline at end of file +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_WIN_TASKBAR_H +#define TAIGA_WIN_TASKBAR_H + +#include + +namespace win { + +class Taskbar { +public: + Taskbar(); + ~Taskbar(); + + BOOL Create(HWND hwnd, HICON icon, LPCWSTR tooltip); + BOOL Destroy(); + BOOL Modify(LPCWSTR tip); + BOOL Tip(LPCWSTR text, LPCWSTR title, int icon_index); + +private: + HWND hwnd_; + NOTIFYICONDATA data_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class TaskbarList { +public: + TaskbarList(); + ~TaskbarList(); + + void Initialize(HWND hwnd); + void Release(); + void SetProgressState(TBPFLAG flag); + void SetProgressValue(ULONGLONG value, ULONGLONG total); + +private: + HWND hwnd_; + ITaskbarList3* taskbar_list_; +}; + +} // namespace win + +//////////////////////////////////////////////////////////////////////////////// + +extern class win::Taskbar Taskbar; +extern class win::TaskbarList TaskbarList; + +extern const DWORD WM_TASKBARCALLBACK; +extern const DWORD WM_TASKBARCREATED; +extern const DWORD WM_TASKBARBUTTONCREATED; + +#endif // TAIGA_WIN_TASKBAR_H \ No newline at end of file diff --git a/src/win/win_taskdialog.cpp b/src/win/win_taskdialog.cpp new file mode 100644 index 000000000..5973c0a3d --- /dev/null +++ b/src/win/win_taskdialog.cpp @@ -0,0 +1,269 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_taskdialog.h" + +namespace win { + +std::vector button_text; + +LRESULT CALLBACK MsgBoxHookProc(int nCode, WPARAM wParam, LPARAM lParam); + +TaskDialog::TaskDialog() { + Initialize(); +} + +TaskDialog::TaskDialog(LPCWSTR title, LPWSTR icon) { + Initialize(); + SetWindowTitle(title); + SetMainIcon(icon); +} + +HRESULT CALLBACK TaskDialog::Callback(HWND hwnd, UINT uNotification, + WPARAM wParam, LPARAM lParam, + LONG_PTR dwRefData) { + switch (uNotification) { + case TDN_DIALOG_CONSTRUCTED: +// ::SetForegroundWindow(hwnd); +// ::BringWindowToTop(hwnd); + break; + case TDN_HYPERLINK_CLICKED: + ::ShellExecute(nullptr, nullptr, reinterpret_cast(lParam), + nullptr, nullptr, SW_SHOWNORMAL); + break; + case TDN_VERIFICATION_CLICKED: { + auto dlg = reinterpret_cast(dwRefData); + dlg->verification_checked_ = static_cast(wParam) == TRUE; + break; + } + } + + return S_OK; +} + +void TaskDialog::Initialize() { + ::ZeroMemory(&config_, sizeof(TASKDIALOGCONFIG)); + + config_.cbSize = sizeof(TASKDIALOGCONFIG); + config_.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | + TDF_ENABLE_HYPERLINKS | + TDF_POSITION_RELATIVE_TO_WINDOW | + TDF_SIZE_TO_CONTENT; + config_.hInstance = ::GetModuleHandle(nullptr); + config_.lpCallbackData = reinterpret_cast(this); + config_.pfCallback = Callback; + + selected_button_id_ = 0; + verification_checked_ = false; +} + +//////////////////////////////////////////////////////////////////////////////// + +void TaskDialog::AddButton(LPCWSTR text, int id) { + buttons_.resize(buttons_.size() + 1); + buttons_.back().pszButtonText = text; + buttons_.back().nButtonID = id; +} + +int TaskDialog::GetSelectedButtonID() const { + return selected_button_id_; +} + +bool TaskDialog::GetVerificationCheck() const { + return verification_checked_; +} + +void TaskDialog::SetCollapsedControlText(LPCWSTR text) { + config_.pszCollapsedControlText = text; +} + +void TaskDialog::SetContent(LPCWSTR text) { + config_.pszContent = text; +} + +void TaskDialog::SetExpandedControlText(LPCWSTR text) { + config_.pszExpandedControlText = text; +} + +void TaskDialog::SetExpandedInformation(LPCWSTR text) { + config_.pszExpandedInformation = text; +} + +void TaskDialog::SetFooter(LPCWSTR text) { + config_.pszFooter = text; +} + +void TaskDialog::SetFooterIcon(LPWSTR icon) { + config_.dwFlags &= ~TDF_USE_HICON_FOOTER; + config_.pszFooterIcon = icon; +} + +void TaskDialog::SetMainIcon(LPWSTR icon) { + config_.dwFlags &= ~TDF_USE_HICON_MAIN; + config_.pszMainIcon = icon; +} + +void TaskDialog::SetMainInstruction(LPCWSTR text) { + config_.pszMainInstruction = text; +} + +void TaskDialog::SetVerificationText(LPCWSTR text) { + config_.pszVerificationText = text; +} + +void TaskDialog::SetWindowTitle(LPCWSTR text) { + config_.pszWindowTitle = text; +} + +void TaskDialog::UseCommandLinks(bool use) { + if (use) { + config_.dwFlags |= TDF_USE_COMMAND_LINKS; + } else { + config_.dwFlags &= ~TDF_USE_COMMAND_LINKS; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +HRESULT TaskDialog::Show(HWND parent) { + config_.hwndParent = parent; + if (!buttons_.empty()) { + config_.pButtons = &buttons_[0]; + config_.cButtons = static_cast(buttons_.size()); + if (buttons_.size() > 1) + config_.dwFlags &= ~TDF_ALLOW_DIALOG_CANCELLATION; + } + + // Show task dialog, if available + if (GetVersion() >= kVersionVista) { + BOOL verification_flag_checked = TRUE; + return ::TaskDialogIndirect(&config_, &selected_button_id_, nullptr, + &verification_flag_checked); + + // Fall back to normal message box + } else { + MSGBOXPARAMS msgbox; + msgbox.cbSize = sizeof(MSGBOXPARAMS); + msgbox.hwndOwner = parent; + msgbox.hInstance = config_.hInstance; + msgbox.lpszCaption = config_.pszWindowTitle; + + // Set message + #define ADD_MSG(x) \ + if (x) { msg += L"\n\n"; msg += x; } + std::wstring msg = config_.pszMainInstruction; + ADD_MSG(config_.pszContent); + ADD_MSG(config_.pszExpandedInformation); + ADD_MSG(config_.pszFooter); + #undef ADD_MSG + msgbox.lpszText = msg.c_str(); + + // Set buttons + button_text.resize(config_.cButtons); + for (unsigned int i = 0; i < button_text.size(); i++) { + button_text[i] = buttons_[i].pszButtonText; + unsigned int pos = button_text[i].find(L"\n"); + if (pos != std::wstring::npos) + button_text[i].resize(pos); + } + switch (config_.cButtons) { + case 2: + msgbox.dwStyle = MB_YESNO; + break; + case 3: + msgbox.dwStyle = MB_YESNOCANCEL; + break; + default: + msgbox.dwStyle = MB_OK; + break; + } + + // Set icon + if (config_.pszMainIcon == TD_ICON_INFORMATION || + config_.pszMainIcon == TD_ICON_SHIELD || + config_.pszMainIcon == TD_ICON_SHIELD_GREEN) { + msgbox.dwStyle |= + (config_.cButtons > 1 ? MB_ICONQUESTION : MB_ICONINFORMATION); + } else if (config_.pszMainIcon == TD_ICON_WARNING) { + msgbox.dwStyle |= MB_ICONWARNING; + } else if (config_.pszMainIcon == TD_ICON_ERROR || + config_.pszMainIcon == TD_ICON_SHIELD_RED) { + msgbox.dwStyle |= MB_ICONERROR; + } + + // Hook + if (!parent) + parent = ::GetDesktopWindow(); + ::SetProp(parent, L"MsgBoxHook", ::SetWindowsHookEx(WH_CALLWNDPROCRET, + MsgBoxHookProc, nullptr, ::GetCurrentThreadId())); + + // Show message box + selected_button_id_ = ::MessageBoxIndirect(&msgbox); + + // Unhook + if (::GetProp(parent, L"MsgBoxHook")) + ::UnhookWindowsHookEx(reinterpret_cast(::RemoveProp( + parent, L"MsgBoxHook"))); + + return S_OK; + } +} + +// MessageBox hook - used to change button text. +// Note that button width is fixed and does not change according to text length. +LRESULT CALLBACK MsgBoxHookProc(int nCode, WPARAM wParam, LPARAM lParam) { + auto cwpr = reinterpret_cast(lParam); + HWND hwnd = cwpr->hwnd; + + if (!(nCode < 0)) { + WCHAR class_name[MAX_PATH]; + GetClassName(hwnd, class_name, MAX_PATH); + + if (!lstrcmpi(class_name, L"#32770")) { + switch (cwpr->message) { + case WM_INITDIALOG: + #define SETBUTTONTEXT(x, y) \ + SendMessage(GetDlgItem(hwnd, x), WM_SETTEXT, 0, \ + reinterpret_cast(y.c_str())) + switch (button_text.size()) { + case 2: + SETBUTTONTEXT(IDYES, button_text[0]); + SETBUTTONTEXT(IDNO, button_text[1]); + break; + case 3: + SETBUTTONTEXT(IDYES, button_text[0]); + SETBUTTONTEXT(IDNO, button_text[1]); + SETBUTTONTEXT(IDCANCEL, button_text[2]); + break; + default: + SETBUTTONTEXT(IDCANCEL, button_text[0]); + } + #undef SETBUTTONTEXT + } + } + } + + HWND parent = GetParent(hwnd); + if (parent == nullptr) + parent = GetDesktopWindow(); + + return CallNextHookEx(reinterpret_cast(GetProp(parent, L"MsgBoxHook")), + nCode, wParam, lParam); +} + +} // namespace win \ No newline at end of file diff --git a/src/win/win_taskdialog.h b/src/win/win_taskdialog.h new file mode 100644 index 000000000..3dd2296d1 --- /dev/null +++ b/src/win/win_taskdialog.h @@ -0,0 +1,74 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_WIN_TASKDIALOG_H +#define TAIGA_WIN_TASKDIALOG_H + +#include "win_main.h" + +#define TD_ICON_NONE static_cast(0) +#define TD_ICON_INFORMATION TD_INFORMATION_ICON +#define TD_ICON_WARNING TD_WARNING_ICON +#define TD_ICON_ERROR TD_ERROR_ICON +#define TD_ICON_SHIELD TD_SHIELD_ICON +#define TD_ICON_SHIELD_GREEN MAKEINTRESOURCE(-8) +#define TD_ICON_SHIELD_RED MAKEINTRESOURCE(-7) + +#define TDF_SIZE_TO_CONTENT 0x1000000 + +namespace win { + +class TaskDialog { +public: + TaskDialog(); + TaskDialog(LPCWSTR title, LPWSTR icon); + virtual ~TaskDialog() {} + + void AddButton(LPCWSTR text, int id); + int GetSelectedButtonID() const; + bool GetVerificationCheck() const; + void SetCollapsedControlText(LPCWSTR text); + void SetContent(LPCWSTR text); + void SetExpandedControlText(LPCWSTR text); + void SetExpandedInformation(LPCWSTR text); + void SetFooter(LPCWSTR LPCWSTR); + void SetFooterIcon(LPWSTR icon); + void SetMainIcon(LPWSTR icon); + void SetMainInstruction(LPCWSTR text); + void SetVerificationText(LPCWSTR text); + void SetWindowTitle(LPCWSTR text); + HRESULT Show(HWND parent); + void UseCommandLinks(bool use); + +protected: + static HRESULT CALLBACK Callback( + HWND hwnd, UINT uNotification, + WPARAM wParam, LPARAM lParam, + LONG_PTR dwRefData); + + void Initialize(); + + std::vector buttons_; + TASKDIALOGCONFIG config_; + int selected_button_id_; + bool verification_checked_; +}; + +} // namespace win + +#endif // TAIGA_WIN_TASKDIALOG_H \ No newline at end of file diff --git a/src/win/win_thread.cpp b/src/win/win_thread.cpp new file mode 100644 index 000000000..53acb2b13 --- /dev/null +++ b/src/win/win_thread.cpp @@ -0,0 +1,163 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_main.h" +#include "win_thread.h" + +namespace win { + +//////////////////////////////////////////////////////////////////////////////// + +Thread::Thread() + : thread_(nullptr), + thread_id_(0) { +} + +Thread::~Thread() { + CloseThreadHandle(); +} + +bool Thread::CloseThreadHandle() { + BOOL value = TRUE; + + if (thread_) { + value = ::CloseHandle(thread_); + thread_ = nullptr; + } + + return value != FALSE; +} + +bool Thread::CreateThread(LPSECURITY_ATTRIBUTES thread_attributes, + SIZE_T stack_size, + DWORD creation_flags) { + thread_ = ::CreateThread(thread_attributes, stack_size, ThreadProcStatic, + this, creation_flags, &thread_id_); + + return thread_ != nullptr; +} + +HANDLE Thread::GetThreadHandle() const { + return thread_; +} + +DWORD Thread::GetThreadId() const { + return thread_id_; +} + +DWORD Thread::ThreadProc() { + return 0; +} + +DWORD WINAPI Thread::ThreadProcStatic(LPVOID lpParam) { + Thread* thread = reinterpret_cast(lpParam); + return thread->ThreadProc(); +} + +//////////////////////////////////////////////////////////////////////////////// + +CriticalSection::CriticalSection() { + ::InitializeCriticalSectionAndSpinCount(&critical_section_, 0); +} + +CriticalSection::~CriticalSection() { + ::DeleteCriticalSection(&critical_section_); +} + +void CriticalSection::Enter() { + ::EnterCriticalSection(&critical_section_); +} + +void CriticalSection::Leave() { + ::LeaveCriticalSection(&critical_section_); +} + +bool CriticalSection::TryEnter() { + return ::TryEnterCriticalSection(&critical_section_) != FALSE; +} + +void CriticalSection::Wait() { + while (!TryEnter()) + ::Sleep(1); +} + +//////////////////////////////////////////////////////////////////////////////// + +Event::Event() + : event_(nullptr) { +} + +Event::~Event() { + if (event_) + ::CloseHandle(event_); +} + +HANDLE Event::Create(LPSECURITY_ATTRIBUTES event_attributes, BOOL manual_reset, + BOOL initial_state, LPCTSTR name) { + event_ = ::CreateEvent(event_attributes, manual_reset, initial_state, name); + + return event_; +} + +//////////////////////////////////////////////////////////////////////////////// + +Lock::Lock(CriticalSection& critical_section) + : critical_section_(critical_section) { + critical_section_.Enter(); +} + +Lock::~Lock() { + critical_section_.Leave(); +} + +//////////////////////////////////////////////////////////////////////////////// + +Mutex::Mutex() + : mutex_(nullptr) { +} + +Mutex::~Mutex() { + if (mutex_) + ::CloseHandle(mutex_); +} + +HANDLE Mutex::Create(LPSECURITY_ATTRIBUTES mutex_attributes, + BOOL initial_owner, LPCTSTR name) { + mutex_ = ::CreateMutex(mutex_attributes, initial_owner, name); + + return mutex_; +} + +HANDLE Mutex::Open(DWORD desired_access, BOOL inherit_handle, LPCTSTR name) { + mutex_ = ::OpenMutex(desired_access, inherit_handle, name); + + return mutex_; +} + +bool Mutex::Release() { + BOOL value = TRUE; + + if (mutex_) { + value = ::ReleaseMutex(mutex_); + mutex_ = nullptr; + } + + return value != FALSE; +} + +} // namespace win \ No newline at end of file diff --git a/src/win/win_thread.h b/src/win/win_thread.h new file mode 100644 index 000000000..58a210fff --- /dev/null +++ b/src/win/win_thread.h @@ -0,0 +1,110 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_WIN_THREAD_H +#define TAIGA_WIN_THREAD_H + +#include "win_main.h" + +namespace win { + +class Thread { +public: + Thread(); + virtual ~Thread(); + + virtual DWORD ThreadProc(); + + bool CloseThreadHandle(); + bool CreateThread( + LPSECURITY_ATTRIBUTES thread_attributes, + SIZE_T stack_size, + DWORD creation_flags); + + HANDLE GetThreadHandle() const; + DWORD GetThreadId() const; + +private: + static DWORD WINAPI ThreadProcStatic(LPVOID thread); + + HANDLE thread_; + DWORD thread_id_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class CriticalSection { +public: + CriticalSection(); + virtual ~CriticalSection(); + + void Enter(); + void Leave(); + bool TryEnter(); + void Wait(); + +private: + CRITICAL_SECTION critical_section_; +}; + +class Event { +public: + Event(); + virtual ~Event(); + + HANDLE Create( + LPSECURITY_ATTRIBUTES event_attributes, + BOOL manual_reset, + BOOL initial_state, + LPCTSTR name); + +private: + HANDLE event_; +}; + +class Lock { +public: + Lock(CriticalSection& critical_section); + virtual ~Lock(); + +private: + CriticalSection& critical_section_; +}; + +class Mutex { +public: + Mutex(); + virtual ~Mutex(); + + HANDLE Create( + LPSECURITY_ATTRIBUTES mutex_attributes, + BOOL initial_owner, + LPCTSTR name); + HANDLE Open( + DWORD desired_access, + BOOL inherit_handle, + LPCTSTR name); + bool Release(); + +private: + HANDLE mutex_; +}; + +} // namespace win + +#endif // TAIGA_WIN_THREAD_H \ No newline at end of file diff --git a/src/win/win_window.cpp b/src/win/win_window.cpp new file mode 100644 index 000000000..e9079b637 --- /dev/null +++ b/src/win/win_window.cpp @@ -0,0 +1,742 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include "win_main.h" +#include "win_taskbar.h" +#include "win_window.h" + +namespace win { + +const int kControlMargin = 6; +const std::wstring kDefaultClassName = L"TaigaDefaultW"; + +Window* Window::current_window_ = nullptr; + +Window::Window() + : instance_(::GetModuleHandle(nullptr)), + font_(nullptr), icon_large_(nullptr), icon_small_(nullptr), + menu_(nullptr), parent_(nullptr), window_(nullptr) { + current_window_ = nullptr; + + ::ZeroMemory(&create_struct_, sizeof(CREATESTRUCT)); + ::ZeroMemory(&window_class_, sizeof(WNDCLASSEX)); + + // Create default window class + WNDCLASSEX wc = {0}; + if (!::GetClassInfoEx(instance_, kDefaultClassName.c_str(), &wc)) { + wc.cbSize = sizeof(wc); + wc.style = CS_DBLCLKS; + wc.lpfnWndProc = WindowProcStatic; + wc.hInstance = instance_; + wc.hCursor = ::LoadCursor(nullptr, IDC_ARROW); + wc.hbrBackground = reinterpret_cast(::GetStockObject(WHITE_BRUSH)); + wc.lpszClassName = kDefaultClassName.c_str(); + ::RegisterClassEx(&wc); + } +} + +Window::Window(HWND hwnd) + : instance_(::GetModuleHandle(nullptr)), + font_(nullptr), icon_large_(nullptr), icon_small_(nullptr), + menu_(nullptr), parent_(nullptr), window_(nullptr) { + current_window_ = nullptr; + window_ = hwnd; +} + +Window::~Window() { + Destroy(); +} + +//////////////////////////////////////////////////////////////////////////////// + +HWND Window::Create(HWND parent) { + PreRegisterClass(window_class_); + if (window_class_.lpszClassName) { + RegisterClass(window_class_); + create_struct_.lpszClass = window_class_.lpszClassName; + } + + PreCreate(create_struct_); + if (!create_struct_.lpszClass) { + create_struct_.lpszClass = kDefaultClassName.c_str(); + } + if (!parent && create_struct_.hwndParent) { + parent = create_struct_.hwndParent; + } + DWORD style; + if (create_struct_.style) { + style = create_struct_.style; + } else { + style = WS_VISIBLE | (parent ? WS_CHILD : WS_OVERLAPPEDWINDOW); + } + + bool cx_or_cy = create_struct_.cx || create_struct_.cy; + int x = cx_or_cy ? create_struct_.x : CW_USEDEFAULT; + int y = cx_or_cy ? create_struct_.y : CW_USEDEFAULT; + int cx = cx_or_cy ? create_struct_.cx : CW_USEDEFAULT; + int cy = cx_or_cy ? create_struct_.cy : CW_USEDEFAULT; + + return Create(create_struct_.dwExStyle, + create_struct_.lpszClass, + create_struct_.lpszName, + style, + x, y, cx, cy, + parent, + create_struct_.hMenu, + create_struct_.lpCreateParams); +} + +HWND Window::Create(DWORD ex_style, LPCWSTR class_name, LPCWSTR window_name, + DWORD style, int x, int y, int width, int height, + HWND parent, HMENU menu, LPVOID param) { + Destroy(); + + current_window_ = this; + + menu_ = menu; + parent_ = parent; + + window_ = ::CreateWindowEx(ex_style, class_name, window_name, + style, x, y, width, height, + parent, menu, instance_, param); + + WNDCLASSEX wc = {0}; + ::GetClassInfoEx(instance_, class_name, &wc); + if (wc.lpfnWndProc != reinterpret_cast(WindowProcStatic)) { + Subclass(window_); + OnCreate(window_, &create_struct_); + } + + current_window_ = nullptr; + + return window_; +} + +void Window::Destroy() { + if (::IsWindow(window_)) + ::DestroyWindow(window_); + + if (font_ && parent_) { + ::DeleteObject(font_); + font_ = nullptr; + } + if (icon_large_) { + ::DestroyIcon(icon_large_); + icon_large_ = nullptr; + } + if (icon_small_) { + ::DestroyIcon(icon_small_); + icon_small_ = nullptr; + } + + if (prev_window_proc_) { + UnSubclass(); + prev_window_proc_ = nullptr; + } + + WindowMap.Remove(this); + window_ = nullptr; +} + +void Window::PreCreate(CREATESTRUCT& cs) { + create_struct_.cx = cs.cx; + create_struct_.cy = cs.cy; + create_struct_.dwExStyle = cs.dwExStyle; + create_struct_.hInstance = instance_; + create_struct_.hMenu = cs.hMenu; + create_struct_.hwndParent = cs.hwndParent; + create_struct_.lpCreateParams = cs.lpCreateParams; + create_struct_.lpszClass = cs.lpszClass; + create_struct_.lpszName = cs.lpszName; + create_struct_.style = cs.style; + create_struct_.x = cs.x; + create_struct_.y = cs.y; +} + +void Window::PreRegisterClass(WNDCLASSEX& wc) { + window_class_.style = wc.style; + window_class_.lpfnWndProc = WindowProcStatic; + window_class_.cbClsExtra = wc.cbClsExtra; + window_class_.cbWndExtra = wc.cbWndExtra; + window_class_.hInstance = instance_; + window_class_.hIcon = wc.hIcon; + window_class_.hCursor = wc.hCursor; + window_class_.hbrBackground = wc.hbrBackground; + window_class_.lpszMenuName = wc.lpszMenuName; + window_class_.lpszClassName = wc.lpszClassName; +} + +BOOL Window::PreTranslateMessage(MSG* msg) { + return FALSE; +} + +BOOL Window::RegisterClass(WNDCLASSEX& wc) const { + WNDCLASSEX wc_existing = {0}; + if (::GetClassInfoEx(instance_, wc.lpszClassName, &wc_existing)) { + wc = wc_existing; + return TRUE; + } + + wc.cbSize = sizeof(wc); + wc.hInstance = instance_; + wc.lpfnWndProc = WindowProcStatic; + + return ::RegisterClassEx(&wc); +} + +//////////////////////////////////////////////////////////////////////////////// + +void Window::Attach(HWND hwnd) { + Detach(); + + if (::IsWindow(hwnd)) { + if (!WindowMap.GetWindow(hwnd)) { + WindowMap.Add(hwnd, this); + Subclass(hwnd); + } + } +} + +void Window::CenterOwner() { + GetParent(); + + RECT rc_desktop, rc_parent, rc_window; + ::GetWindowRect(window_, &rc_window); + ::SystemParametersInfo(SPI_GETWORKAREA, 0, &rc_desktop, 0); + if (parent_) { + ::GetWindowRect(parent_, &rc_parent); + } else { + ::CopyRect(&rc_parent, &rc_desktop); + } + + HMONITOR monitor = ::MonitorFromWindow(window_, MONITOR_DEFAULTTONEAREST); + MONITORINFO mi = { sizeof(mi), 0 }; + if (::GetMonitorInfo(monitor, &mi)) { + rc_desktop = mi.rcWork; + if (!parent_) + rc_parent = mi.rcWork; + } + + ::IntersectRect(&rc_parent, &rc_parent, &rc_desktop); + + int parent_width = rc_parent.right - rc_parent.left; + int parent_height = rc_parent.bottom - rc_parent.top; + int window_width = rc_window.right - rc_window.left; + int window_height = rc_window.bottom - rc_window.top; + + ::SetWindowPos(window_, HWND_TOP, + rc_parent.left + ((parent_width - window_width) / 2), + rc_parent.top + ((parent_height - window_height) / 2), + 0, 0, SWP_NOSIZE); +} + +HWND Window::Detach() { + HWND hwnd = window_; + + if (prev_window_proc_) + UnSubclass(); + + WindowMap.Remove(this); + + window_ = nullptr; + + return hwnd; +} + +LPCWSTR Window::GetClassName() const { + return window_class_.lpszClassName; +} + +HMENU Window::GetMenuHandle() const { + return menu_; +} + +HWND Window::GetParentHandle() const { + return parent_; +} + +HICON Window::SetIconLarge(HICON icon) { + if (icon_large_) + ::DestroyIcon(icon_large_); + + icon_large_ = icon; + + if (!icon_large_) + return nullptr; + + return reinterpret_cast(SendMessage( + WM_SETICON, ICON_BIG, reinterpret_cast(icon_large_))); +} + +HICON Window::SetIconLarge(int icon) { + return SetIconLarge(reinterpret_cast(LoadImage( + instance_, MAKEINTRESOURCE(icon), IMAGE_ICON, + ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON), + LR_DEFAULTCOLOR))); +} + +HICON Window::SetIconSmall(HICON icon) { + if (icon_small_) + ::DestroyIcon(icon_small_); + + icon_small_ = icon; + + if (!icon_small_) + return nullptr; + + return reinterpret_cast(SendMessage( + WM_SETICON, ICON_SMALL, reinterpret_cast(icon_small_))); +} + +HICON Window::SetIconSmall(int icon) { + return SetIconSmall(reinterpret_cast(LoadImage( + instance_, MAKEINTRESOURCE(icon), IMAGE_ICON, + ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), + LR_DEFAULTCOLOR))); +} + +HWND Window::GetWindowHandle() const { + return window_; +} + +void Window::SetWindowHandle(HWND hwnd) { + window_ = hwnd; +} + +//////////////////////////////////////////////////////////////////////////////// +// Win32 API wrappers + +BOOL Window::BringWindowToTop() const { + return ::BringWindowToTop(window_); +} + +BOOL Window::Close() const { + return ::CloseWindow(window_); +} + +BOOL Window::Enable(BOOL enable) const { + return ::EnableWindow(window_, enable); +} + +BOOL Window::GetClientRect(LPRECT rect) const { + return ::GetClientRect(window_, rect); +} + +HDC Window::GetDC() const { + return ::GetDC(window_); +} + +HFONT Window::GetFont() const { + return reinterpret_cast(SendMessage(WM_GETFONT, 0, 0)); +} + +HMENU Window::GetMenu() const { + return ::GetMenu(window_); +} + +HWND Window::GetParent() { + parent_ = ::GetParent(window_); + if (!parent_) + parent_ = ::GetDesktopWindow(); + return parent_; +} + +void Window::GetText(LPWSTR output, int max_count) const { + ::GetWindowText(window_, output, max_count); +} + +void Window::GetText(std::wstring& output) const { + int len = ::GetWindowTextLength(window_) + 1; + std::vector buffer(len); + ::GetWindowText(window_, &buffer[0], len); + output.assign(&buffer[0]); +} + +std::wstring Window::GetText() const { + int len = ::GetWindowTextLength(window_) + 1; + std::vector buffer(len); + ::GetWindowText(window_, &buffer[0], len); + return std::wstring(&buffer[0]); +} + +INT Window::GetTextLength() const { + return ::GetWindowTextLength(window_); +} + +DWORD Window::GetWindowLong(int index) const { + return ::GetWindowLong(window_, index); +} + +BOOL Window::GetWindowRect(LPRECT rect) const { + return ::GetWindowRect(window_, rect); +} + +void Window::GetWindowRect(HWND hwnd_to, LPRECT rect) const { + ::GetClientRect(window_, rect); + int width = rect->right; + int height = rect->bottom; + + ::GetWindowRect(window_, rect); + ::ScreenToClient(hwnd_to, reinterpret_cast(rect)); + + rect->right = rect->left + width; + rect->bottom = rect->top + height; +} + +BOOL Window::Hide() const { + return ::ShowWindow(window_, SW_HIDE); +} + +BOOL Window::InvalidateRect(LPCRECT rect, BOOL erase) const { + return ::InvalidateRect(window_, rect, erase); +} + +BOOL Window::IsEnabled() const { + return ::IsWindowEnabled(window_); +} + +BOOL Window::IsIconic() const { + return ::IsIconic(window_); +} + +BOOL Window::IsVisible() const { + return ::IsWindowVisible(window_); +} + +BOOL Window::IsWindow() const { + return ::IsWindow(window_); +} + +INT Window::MessageBox(LPCWSTR text, LPCWSTR caption, UINT type) const { + return ::MessageBox(window_, text, caption, type); +} + +LRESULT Window::PostMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) const { + return ::PostMessage(window_, uMsg, wParam, lParam); +} + +BOOL Window::RedrawWindow(LPCRECT rect, HRGN region, UINT flags) const { + return ::RedrawWindow(window_, rect, region, flags); +} + +LRESULT Window::SendMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) const { + return ::SendMessage(window_, uMsg, wParam, lParam); +} + +BOOL Window::SetBorder(int style) const { + switch (style) { + case kWindowBorderNone: + SetStyle(0, WS_EX_CLIENTEDGE | WS_EX_STATICEDGE, GWL_EXSTYLE); + break; + case kWindowBorderClient: + SetStyle(WS_EX_CLIENTEDGE, WS_EX_STATICEDGE, GWL_EXSTYLE); + break; + case kWindowBorderStatic: + SetStyle(WS_EX_STATICEDGE, WS_EX_CLIENTEDGE, GWL_EXSTYLE); + break; + } + + UINT flags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED; + + return ::SetWindowPos(window_, nullptr, 0, 0, 0, 0, flags); +} + +HWND Window::SetCapture() const { + return ::SetCapture(window_); +} + +DWORD Window::SetClassLong(int index, LONG new_long) const { + return ::SetClassLong(window_, index, new_long); +} + +HWND Window::SetFocus() const { + return ::SetFocus(window_); +} + +void Window::SetFont(LPCWSTR face_name, int size, + bool bold, bool italic, bool underline) { + HDC hdc = ::GetDC(window_); + HFONT font_old = reinterpret_cast(::GetCurrentObject(hdc, OBJ_FONT)); + LOGFONT logfont; + ::GetObject(font_old, sizeof(LOGFONT), &logfont); + + if (face_name) { + ::lstrcpy(logfont.lfFaceName, face_name); + } + if (size) { + logfont.lfHeight = -MulDiv(size, GetDeviceCaps(hdc, LOGPIXELSY), 72); + logfont.lfWidth = 0; + } + logfont.lfItalic = italic; + logfont.lfWeight = bold ? FW_BOLD : FW_NORMAL; + logfont.lfUnderline = underline; + + if (font_) + ::DeleteObject(font_); + font_ = ::CreateFontIndirect(&logfont); + SendMessage(WM_SETFONT, reinterpret_cast(font_), TRUE); + + ::DeleteObject(font_old); + ::ReleaseDC(window_, hdc); +} + +void Window::SetFont(HFONT font) { + if (font_) + ::DeleteObject(font_); + font_ = font; + SendMessage(WM_SETFONT, reinterpret_cast(font_), TRUE); +} + +BOOL Window::SetForegroundWindow() const { + return ::SetForegroundWindow(window_); +} + +BOOL Window::SetMenu(HMENU menu) const { + return ::SetMenu(window_, menu); +} + +void Window::SetParent(HWND parent) const { + if (!parent) { + ::SetParent(window_, parent); + SetStyle(WS_POPUP, WS_CHILD); + } else { + SetStyle(WS_CHILD, WS_POPUP); + ::SetParent(window_, parent); + } +} + +BOOL Window::SetPosition(HWND hwnd_insert_after, int x, int y, int w, int h, + UINT flags) const { + return ::SetWindowPos(window_, hwnd_insert_after, x, y, w, h, flags); +} + +BOOL Window::SetPosition(HWND hwnd_insert_after, const RECT& rc, + UINT flags) const { + return ::SetWindowPos(window_, hwnd_insert_after, + rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, + flags); +} + +BOOL Window::SetRedraw(BOOL redraw) const { + return ::SendMessage(window_, WM_SETREDRAW, redraw, 0); +} + +void Window::SetStyle(UINT style, UINT style_not, int index) const { + UINT old_style = ::GetWindowLong(window_, index); + ::SetWindowLongPtr(window_, index, (old_style | style) & ~style_not); +} + +BOOL Window::SetText(LPCWSTR text) const { + return SendMessage(WM_SETTEXT, 0, reinterpret_cast(text)); +} + +BOOL Window::SetText(const std::wstring& text) const { + return SetText(text.c_str()); +} + +HRESULT Window::SetTheme(LPCWSTR theme_name) const { + return ::SetWindowTheme(window_, theme_name, nullptr); +} + +BOOL Window::SetTransparency(BYTE alpha, COLORREF color) const { + if (alpha == 255) { + SetStyle(0, WS_EX_LAYERED, GWL_EXSTYLE); + } else { + SetStyle(WS_EX_LAYERED, 0, GWL_EXSTYLE); + } + + DWORD flags; + if (color & 0xFF000000) { + color = RGB(255, 255, 255); + flags = LWA_ALPHA; + } else { + flags = LWA_COLORKEY; + } + + return ::SetLayeredWindowAttributes(window_, color, alpha, flags); +} + +BOOL Window::Show(int cmd_show) const { + return ::ShowWindow(window_, cmd_show); +} + +BOOL Window::Update() const { + return ::UpdateWindow(window_); +} + +//////////////////////////////////////////////////////////////////////////////// + +void Window::OnCreate(HWND hwnd, LPCREATESTRUCT create_struct) { + LOGFONT logfont; + ::GetObject(::GetStockObject(DEFAULT_GUI_FONT), sizeof(logfont), &logfont); + font_ = ::CreateFontIndirect(&logfont); + ::SendMessage(window_, WM_SETFONT, reinterpret_cast(font_), FALSE); +} + +//////////////////////////////////////////////////////////////////////////////// + +void Window::Subclass(HWND hwnd) { + WNDPROC current_proc = reinterpret_cast( + ::GetWindowLongPtr(hwnd, GWLP_WNDPROC)); + if (current_proc != reinterpret_cast(WindowProcStatic)) { + prev_window_proc_ = reinterpret_cast(::SetWindowLongPtr( + hwnd, GWLP_WNDPROC, reinterpret_cast(WindowProcStatic))); + window_ = hwnd; + } +} + +void Window::UnSubclass() { + WNDPROC current_proc = reinterpret_cast( + ::GetWindowLongPtr(window_, GWLP_WNDPROC)); + if (current_proc == reinterpret_cast(WindowProcStatic)) { + ::SetWindowLongPtr(window_, GWLP_WNDPROC, + reinterpret_cast(prev_window_proc_)); + prev_window_proc_ = nullptr; + } +} + +LRESULT CALLBACK Window::WindowProcStatic(HWND hwnd, UINT uMsg, + WPARAM wParam, LPARAM lParam) { + Window* window = WindowMap.GetWindow(hwnd); + + if (!window) { + window = current_window_; + if (window) { + window->SetWindowHandle(hwnd); + WindowMap.Add(hwnd, window); + } + } + + if (window) { + return window->WindowProc(hwnd, uMsg, wParam, lParam); + } else { + return ::DefWindowProc(hwnd, uMsg, wParam, lParam); + } +} + +LRESULT Window::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + return WindowProcDefault(hwnd, uMsg, wParam, lParam); +} + +LRESULT Window::WindowProcDefault(HWND hwnd, UINT uMsg, + WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_COMMAND: { + if (OnCommand(wParam, lParam)) + return 0; + break; + } + case WM_CREATE: { + OnCreate(hwnd, reinterpret_cast(lParam)); + break; + } + case WM_DESTROY: { + if (OnDestroy()) + return 0; + break; + } + case WM_DROPFILES: { + OnDropFiles(reinterpret_cast(wParam)); + break; + } + case WM_ENTERSIZEMOVE: + case WM_EXITSIZEMOVE: { + SIZE size = {0}; + OnSize(uMsg, 0, size); + break; + } + case WM_GETMINMAXINFO: { + OnGetMinMaxInfo(reinterpret_cast(lParam)); + break; + } + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_MOUSEACTIVATE: + case WM_MOUSEHOVER: + case WM_MOUSEHWHEEL: + case WM_MOUSELEAVE: + case WM_MOUSEMOVE: + case WM_MOUSEWHEEL: { + LRESULT lResult = OnMouseEvent(uMsg, wParam, lParam); + if (lResult != -1) + return lResult; + break; + } + case WM_MOVE: { + POINTS pts = MAKEPOINTS(lParam); + OnMove(&pts); + break; + } + case WM_NOTIFY: { + LRESULT lResult = OnNotify(static_cast(wParam), + reinterpret_cast(lParam)); + if (lResult) + return lResult; + break; + } + case WM_PAINT: { + if (!prev_window_proc_) { + if (::GetUpdateRect(hwnd, nullptr, FALSE)) { + PAINTSTRUCT ps; + HDC hdc = ::BeginPaint(hwnd, &ps); + OnPaint(hdc, &ps); + ::EndPaint(hwnd, &ps); + } else { + HDC hdc = ::GetDC(hwnd); + OnPaint(hdc, nullptr); + ::ReleaseDC(hwnd, hdc); + } + } + break; + } + case WM_SIZE: { + SIZE size = {LOWORD(lParam), HIWORD(lParam)}; + OnSize(uMsg, static_cast(wParam), size); + break; + } + case WM_TIMER: { + OnTimer(static_cast(wParam)); + break; + } + case WM_WINDOWPOSCHANGING: { + OnWindowPosChanging(reinterpret_cast(lParam)); + break; + } + default: { + if (uMsg == WM_TASKBARCREATED || + uMsg == WM_TASKBARBUTTONCREATED || + uMsg == WM_TASKBARCALLBACK) { + OnTaskbarCallback(uMsg, lParam); + return 0; + } + break; + } + } + + if (prev_window_proc_) { + return ::CallWindowProc(prev_window_proc_, hwnd, uMsg, wParam, lParam); + } else { + return ::DefWindowProc(hwnd, uMsg, wParam, lParam); + } +} + +} // namespace win \ No newline at end of file diff --git a/src/win/win_window.h b/src/win/win_window.h new file mode 100644 index 000000000..3c2c56335 --- /dev/null +++ b/src/win/win_window.h @@ -0,0 +1,145 @@ +/* +** Taiga +** Copyright (C) 2010-2014, Eren Okka +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#ifndef TAIGA_WIN_WINDOW_H +#define TAIGA_WIN_WINDOW_H + +#include "win_main.h" + +namespace win { + +extern const int kControlMargin; + +enum WindowBorderStyle { + kWindowBorderNone, + kWindowBorderClient, + kWindowBorderStatic +}; + +class Window { +public: + Window(); + Window(HWND hwnd); + virtual ~Window(); + + virtual HWND Create(HWND parent = nullptr); + virtual HWND Create(DWORD ex_style, LPCWSTR class_name, LPCWSTR window_name, DWORD style, int x, int y, int width, int height, HWND parent, HMENU menu, LPVOID param); + virtual void Destroy(); + virtual void PreCreate(CREATESTRUCT& cs); + virtual void PreRegisterClass(WNDCLASSEX& wc); + virtual BOOL PreTranslateMessage(MSG* msg); + + void Attach(HWND hwnd); + void CenterOwner(); + HWND Detach(); + LPCWSTR GetClassName() const; + HMENU GetMenuHandle() const; + HWND GetParentHandle() const; + HICON SetIconLarge(HICON icon); + HICON SetIconLarge(int icon); + HICON SetIconSmall(HICON icon); + HICON SetIconSmall(int icon); + HWND GetWindowHandle() const; + void SetWindowHandle(HWND hwnd); + + // Win32 API wrappers + BOOL BringWindowToTop() const; + BOOL Close() const; + BOOL Enable(BOOL enable = TRUE) const; + BOOL GetClientRect(LPRECT rect) const; + HDC GetDC() const; + HFONT GetFont() const; + HMENU GetMenu() const; + HWND GetParent(); + void GetText(LPWSTR output, int max_count = MAX_PATH) const; + void GetText(std::wstring& output) const; + std::wstring GetText() const; + INT GetTextLength() const; + DWORD GetWindowLong(int index = GWL_STYLE) const; + BOOL GetWindowRect(LPRECT rect) const; + void GetWindowRect(HWND hwnd_to, LPRECT rect) const; + BOOL Hide() const; + BOOL InvalidateRect(LPCRECT rect = nullptr, BOOL erase = TRUE) const; + BOOL IsEnabled() const; + BOOL IsIconic() const; + BOOL IsVisible() const; + BOOL IsWindow() const; + INT MessageBox(LPCWSTR text, LPCWSTR caption, UINT type) const; + LRESULT PostMessage(UINT uMsg, WPARAM wParam = 0, LPARAM lParam = 0) const; + BOOL RedrawWindow(LPCRECT rect = nullptr, HRGN region = nullptr, UINT flags = RDW_INVALIDATE | RDW_ALLCHILDREN) const; + LRESULT SendMessage(UINT uMsg, WPARAM wParam = 0, LPARAM lParam = 0) const; + BOOL SetBorder(int style) const; + HWND SetCapture() const; + DWORD SetClassLong(int index, LONG new_long) const; + HWND SetFocus() const; + void SetFont(LPCWSTR face_name, int size, bool bold = false, bool italic = false, bool underline = false); + void SetFont(HFONT font); + BOOL SetForegroundWindow() const; + BOOL SetMenu(HMENU menu) const; + void SetParent(HWND parent) const; + BOOL SetPosition(HWND hwnd_insert_after, int x, int y, int w, int h, UINT flags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER) const; + BOOL SetPosition(HWND hwnd_insert_after, const RECT& rc, UINT flags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER) const; + BOOL SetRedraw(BOOL redraw) const; + void SetStyle(UINT style, UINT style_not, int index = GWL_STYLE) const; + BOOL SetText(LPCWSTR text) const; + BOOL SetText(const std::wstring& text) const; + HRESULT SetTheme(LPCWSTR theme_name = L"explorer") const; + BOOL SetTransparency(BYTE alpha, COLORREF color = 0xFF000000) const; + BOOL Show(int cmd_show = SW_SHOWNORMAL) const; + BOOL Update() const; + +protected: + // Message handlers + virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam) { return FALSE; } + virtual void OnCreate(HWND hwnd, LPCREATESTRUCT create_struct); + virtual BOOL OnDestroy() { return FALSE; } + virtual void OnDropFiles(HDROP drop_info) {} + virtual void OnGetMinMaxInfo(LPMINMAXINFO mmi) {} + virtual LRESULT OnMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) { return -1; } + virtual void OnMove(LPPOINTS pts) {} + virtual LRESULT OnNotify(int control_id, LPNMHDR nmh) { return 0; } + virtual void OnPaint(HDC hdc, LPPAINTSTRUCT ps) {} + virtual void OnSize(UINT uMsg, UINT type, SIZE size) {} + virtual void OnTaskbarCallback(UINT uMsg, LPARAM lParam) {} + virtual void OnTimer(UINT_PTR event_id) {} + virtual void OnWindowPosChanging(LPWINDOWPOS window_pos) {} + virtual LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + virtual LRESULT WindowProcDefault(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + + CREATESTRUCT create_struct_; + WNDCLASSEX window_class_; + HINSTANCE instance_; + HFONT font_; + HICON icon_large_, icon_small_; + HMENU menu_; + HWND parent_, window_; + WNDPROC prev_window_proc_; + +private: + static LRESULT CALLBACK WindowProcStatic(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + + BOOL RegisterClass(WNDCLASSEX& wc) const; + void Subclass(HWND hwnd); + void UnSubclass(); + + static Window* current_window_; +}; + +} // namespace win + +#endif // TAIGA_WIN_WINDOW_H \ No newline at end of file diff --git a/std.h b/std.h deleted file mode 100644 index 6e0fe3df7..000000000 --- a/std.h +++ /dev/null @@ -1,52 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef STD_H -#define STD_H - -#ifdef _MSC_VER -#pragma warning (disable: 4996) -#ifndef _CRT_SECURE_NO_WARNINGS -#define _CRT_SECURE_NO_WARNINGS -#endif -#endif - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -using std::string; -using std::vector; -using std::wstring; - -extern HINSTANCE g_hInstance; -extern HWND g_hMain; - -#endif // STD_H diff --git a/string.cpp b/string.cpp deleted file mode 100644 index 69f941d0e..000000000 --- a/string.cpp +++ /dev/null @@ -1,1050 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" -#include "string.h" -#include -#include -#include -#include - -// ============================================================================= - -/* Erasing */ - -void Erase(wstring& str1, const wstring& str2, bool case_insensitive) { - if (str2.empty() || str1.length() < str2.length()) return; - auto it = str1.begin(); - do { - if (case_insensitive) { - it = std::search(it, str1.end(), str2.begin(), str2.end(), &IsCharsEqual); - } else { - it = std::search(it, str1.end(), str2.begin(), str2.end()); - } - if (it != str1.end()) str1.erase(it, it + str2.length()); - } while (it != str1.end()); -} - -void EraseChars(wstring& str, const wchar_t chars[]) { - size_t pos = 0; - do { - pos = str.find_first_of(chars, pos); - if (pos != wstring::npos) str.erase(pos, 1); - } while (pos != wstring::npos); -} - -void ErasePunctuation(wstring& str, bool keep_trailing) { - auto rlast = str.rbegin(); - if (keep_trailing) { - rlast = std::find_if(str.rbegin(), str.rend(), - [](wchar_t c) -> bool { - return !(c == L'!' || // "Hayate no Gotoku!", "K-ON!"... - c == L'+' || // "Needless+" - c == L'\''); // "Gintama'" - }); - } - - auto it = std::remove_if(str.begin(), rlast.base(), - [](int c) -> bool { - // Control codes, white-space and punctuation characters - if (c <= 255 && !isalnum(c)) return true; - // Unicode stars, hearts, notes, etc. (0x2000-0x2767) - if (c > 8192 && c < 10087) return true; - // Valid character - return false; - }); - - if (keep_trailing) std::copy(rlast.base(), str.end(), it); - str.resize(str.size() - (rlast.base() - it)); -} - -void EraseLeft(wstring& str1, const wstring str2, bool case_insensitive) { - if (str1.length() < str2.length()) return; - if (case_insensitive) { - if (!std::equal(str2.begin(), str2.end(), str1.begin(), &IsCharsEqual)) return; - } else { - if (!std::equal(str2.begin(), str2.end(), str1.begin())) return; - } - str1.erase(str1.begin(), str1.begin() + str2.length()); -} - -void EraseRight(wstring& str1, const wstring str2, bool case_insensitive) { - if (str1.length() < str2.length()) return; - if (case_insensitive) { - if (!std::equal(str2.begin(), str2.end(), str1.end() - str2.length(), &IsCharsEqual)) return; - } else { - if (!std::equal(str2.begin(), str2.end(), str1.end() - str2.length())) return; - } - str1.resize(str1.length() - str2.length()); -} - -void RemoveEmptyStrings(vector& input) { - if (input.empty()) return; - input.erase(std::remove_if(input.begin(), input.end(), - [](const wstring& s) -> bool { return s.empty(); }), - input.end()); -} - -// ============================================================================= - -/* Searching and comparison */ - -wstring CharLeft(const wstring& str, int length) { - return str.substr(0, length); -} - -wstring CharRight(const wstring& str, int length) { - if (length > static_cast(str.length())) { - return str.substr(0, str.length()); - } else { - return str.substr(str.length() - length, length); - } -} - -int CompareStrings(const wstring& str1, const wstring& str2, bool case_insensitive, size_t max_count) { - if (case_insensitive) { - return _wcsnicmp(str1.c_str(), str2.c_str(), max_count); - } else { - return wcsncmp(str1.c_str(), str2.c_str(), max_count); - } -} - -int InStr(const wstring& str1, const wstring str2, int pos, bool case_insensitive) { - if (str1.empty()) return -1; if (str2.empty()) return 0; - if (str1.length() < str2.length()) return -1; - if (case_insensitive) { - auto i = std::search(str1.begin() + pos, str1.end(), str2.begin(), str2.end(), &IsCharsEqual); - return (i == str1.end()) ? -1 : i - str1.begin(); - } else { - size_t i = str1.find(str2, pos); - return (i != wstring::npos) ? i : -1; - } -} - -wstring InStr(const wstring& str1, const wstring& str2_left, const wstring& str2_right) { - wstring output; - int index_begin = InStr(str1, str2_left); - if (index_begin > -1) { - index_begin += str2_left.length(); - int index_end = InStr(str1, str2_right, index_begin); - if (index_end > -1) { - output = str1.substr(index_begin, index_end - index_begin); - } - } - return output; -} - -int InStrRev(const wstring& str1, const wstring str2, int pos) { - size_t i = str1.rfind(str2, pos); - return (i != wstring::npos) ? i : -1; -} - -int InStrChars(const wstring& str1, const wstring str2, int pos) { - size_t i = str1.find_first_of(str2, pos); - return (i != wstring::npos) ? i : -1; -} - -int InStrCharsRev(const wstring& str1, const wstring str2, int pos) { - size_t i = str1.find_last_of(str2, pos); - return (i != wstring::npos) ? i : -1; -} - -bool IsAlphanumeric(const wchar_t c) { - return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); -} -bool IsAlphanumeric(const wstring& str) { - if (str.empty()) return false; - for (size_t i = 0; i < str.length(); i++) - if (!IsAlphanumeric(str[i])) return false; - return true; -} - -inline bool IsCharsEqual(const wchar_t c1, const wchar_t c2) { - return tolower(c1) == tolower(c2); -} - -bool IsEqual(const wstring& str1, const wstring& str2) { - if (str1.length() != str2.length()) return false; - return std::equal(str1.begin(), str1.end(), str2.begin(), &IsCharsEqual); -} - -bool IsHex(const wchar_t c) { - return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); -} -bool IsHex(const wstring& str) { - if (str.empty()) return false; - for (size_t i = 0; i < str.length(); i++) - if (!IsHex(str[i])) return false; - return true; -} - -bool IsNumeric(const wchar_t c) { - return c >= '0' && c <= '9'; -} -bool IsNumeric(const wstring& str) { - if (str.empty()) return false; - for (size_t i = 0; i < str.length(); i++) - if (!IsNumeric(str[i])) return false; - return true; -} - -bool IsWhitespace(const wchar_t c) { - return c == ' ' || c == '\r' || c == '\n' || c == '\t'; -} - -bool StartsWith(const wstring& str1, const wstring& str2) { - return str1.compare(0, str2.length(), str2) == 0; -} - -bool EndsWith(const wstring& str1, const wstring& str2) { - if (str2.length() > str1.length()) return false; - return str1.compare(str1.length() - str2.length(), str2.length(), str2) == 0; -} - -size_t LongestCommonSubsequenceLength(const wstring& str1, const wstring& str2) { - if (str1.empty() || str2.empty()) - return 0; - - const size_t len1 = str1.length(); - const size_t len2 = str2.length(); - - vector> table(len1 + 1); - for (auto it = table.begin(); it != table.end(); ++it) - it->resize(len2 + 1); - - for (size_t i = 0; i < len1; i++) { - for (size_t j = 0; j < len2; j++) { - if (str1[i] == str2[j]) { - table[i + 1][j + 1] = table[i][j] + 1; - } else { - table[i + 1][j + 1] = max(table[i + 1][j], table[i][j + 1]); - } - } - } - - return table.back().back(); -} - -size_t LongestCommonSubstringLength(const wstring& str1, const wstring& str2) { - if (str1.empty() || str2.empty()) - return 0; - - const size_t len1 = str1.length(); - const size_t len2 = str2.length(); - - vector> table(len1); - for (auto it = table.begin(); it != table.end(); ++it) - it->resize(len2); - - size_t longest_length = 0; - - for (size_t i = 0; i < len1; i++) { - for (size_t j = 0; j < len2; j++) { - if (str1[i] == str2[j]) { - if (i == 0 || j == 0) { - table[i][j] = 1; - } else { - table[i][j] = table[i - 1][j - 1] + 1; - } - if (table[i][j] > longest_length) { - longest_length = table[i][j]; - } - } else { - table[i][j] = 0; - } - } - } - - return longest_length; -} - -size_t LevenshteinDistance(const wstring& str1, const wstring& str2) { - const size_t len1 = str1.size(); - const size_t len2 = str2.size(); - - vector prev_col(len2 + 1); - for (size_t i = 0; i < prev_col.size(); i++) - prev_col[i] = i; - - vector col(len2 + 1); - - for (size_t i = 0; i < len1; i++) { - col[0] = i + 1; - - for (size_t j = 0; j < len2; j++) - col[j + 1] = min(min(1 + col[j], 1 + prev_col[1 + j]), - prev_col[j] + (str1[i] == str2[j] ? 0 : 1)); - - col.swap(prev_col); - } - - return prev_col[len2]; -} - -// ============================================================================= - -/* Replace */ - -void Replace(wstring& input, wstring find, wstring replace_with, bool replace_all, bool case_insensitive) { - if (find.empty() || find == replace_with || input.length() < find.length()) return; - if (!case_insensitive) { - for (size_t pos = input.find(find); pos != wstring::npos; pos = input.find(find, pos)) { - input.replace(pos, find.length(), replace_with); - if (!replace_all) pos += replace_with.length(); - } - } else { - for (size_t i = 0; i < input.length() - find.length() + 1; i++) { - for (size_t j = 0; j < find.length(); j++) { - if (input.length() < find.length()) return; - if (tolower(input[i + j]) == tolower(find[j])) { - if (j == find.length() - 1) { - input.replace(i--, find.length(), replace_with); - if (!replace_all) i += replace_with.length(); - break; - } - } else { - i += j; - break; - } - } - } - } -} - -void ReplaceChar(wstring& str, const wchar_t c, const wchar_t replace_with) { - if (c == replace_with) return; - size_t pos = 0; - do { - pos = str.find_first_of(c, pos); - if (pos != wstring::npos) str.at(pos) = replace_with; - } while (pos != wstring::npos); -} - -void ReplaceChars(wstring& str, const wchar_t chars[], const wstring replace_with) { - if (chars == replace_with) return; - size_t pos = 0; - do { - pos = str.find_first_of(chars, pos); - if (pos != wstring::npos) str.replace(pos, 1, replace_with); - } while (pos != wstring::npos); -} - -// ============================================================================= - -/* Split, tokenize */ - -wstring Join(const vector& join_vector, const wstring& separator) { - wstring str; - for (auto it = join_vector.begin(); it != join_vector.end(); ++it) { - if (it != join_vector.begin()) str += separator; - str += *it; - } - return str; -} - -void Split(const wstring& str, const wstring& separator, std::vector& split_vector) { - if (separator.empty()) { - split_vector.push_back(str); - return; - } - size_t index_begin = 0, index_end; - do { - index_end = str.find(separator, index_begin); - if (index_end == wstring::npos) index_end = str.length(); - split_vector.push_back(str.substr(index_begin, index_end - index_begin)); - index_begin = index_end + separator.length(); - } while (index_begin <= str.length()); -} - -wstring SubStr(const wstring& str, const wstring& sub_begin, const wstring& sub_end) { - size_t index_begin = str.find(sub_begin, 0); - if (index_begin == wstring::npos) return L""; - size_t index_end = str.find(sub_end, index_begin); - if (index_end == wstring::npos) index_end = str.length(); - return str.substr(index_begin + 1, index_end - index_begin - 1); -} - -size_t Tokenize(const wstring& str, const wstring& delimiters, vector& tokens) { - tokens.clear(); - size_t index_begin = str.find_first_not_of(delimiters); - while (index_begin != wstring::npos) { - size_t index_end = str.find_first_of(delimiters, index_begin + 1); - if (index_end == wstring::npos) { - tokens.push_back(str.substr(index_begin)); - break; - } else { - tokens.push_back(str.substr(index_begin, index_end - index_begin)); - index_begin = str.find_first_not_of(delimiters, index_end + 1); - } - } - return tokens.size(); -} - -// ============================================================================= - -/* ANSI <-> Unicode conversion */ - -const char* ToANSI(const wstring& str, UINT code_page) { - if (str.empty()) return ""; - int length = WideCharToMultiByte(code_page, 0, str.c_str(), -1, NULL, 0, NULL, NULL); - if (length > 0) { - char* output = new char[length + 1]; - WideCharToMultiByte(code_page, 0, str.c_str(), -1, output, length, NULL, NULL); - return output; - } else { - return ""; - } -} - -wstring ToUTF8(const string& str, UINT code_page) { - if (str.empty()) return L""; - int length = MultiByteToWideChar(code_page, 0, str.c_str(), -1, NULL, 0); - if (length > 0) { - vector output(length); - MultiByteToWideChar(code_page, 0, str.c_str(), -1, &output[0], length); - return &output[0]; - } else { - return L""; - } -} - -// ============================================================================= - -/* Case conversion */ - -// System-default ANSI code page -std::locale current_locale(""); - -void ToLower(wstring& str, bool use_locale) { - if (use_locale) { - std::transform(str.begin(), str.end(), str.begin(), - std::bind2nd(std::ptr_fun(&std::tolower), current_locale)); - } else { - std::transform(str.begin(), str.end(), str.begin(), towlower); - } -} - -wstring ToLower_Copy(wstring str, bool use_locale) { - ToLower(str, use_locale); - return str; -} - -void ToUpper(wstring& str, bool use_locale) { - if (use_locale) { - std::transform(str.begin(), str.end(), str.begin(), - std::bind2nd(std::ptr_fun(&std::toupper), current_locale)); - } else { - std::transform(str.begin(), str.end(), str.begin(), towupper); - } -} - -wstring ToUpper_Copy(wstring str, bool use_locale) { - ToUpper(str, use_locale); - return str; -} - -// ============================================================================= - -/* Type conversion */ - -int ToInt(const wstring& str) { - return _wtoi(str.c_str()); -} - -wstring ToWstr(const INT& value) { - wchar_t buffer[65]; - _ltow_s(value, buffer, 65, 10); - return wstring(buffer); -} - -wstring ToWstr(const ULONG& value) { - wchar_t buffer[65]; - _ultow_s(value, buffer, 65, 10); - return wstring(buffer); -} - -wstring ToWstr(const INT64& value) { - wchar_t buffer[65]; - _i64tow_s(value, buffer, 65, 10); - return wstring(buffer); -} - -wstring ToWstr(const UINT64& value) { - wchar_t buffer[65]; - _ui64tow_s(value, buffer, 65, 10); - return wstring(buffer); -} - -wstring ToWstr(const double& value, int count) { - std::wostringstream out; - out << std::fixed << std::setprecision(count) << value; - return out.str(); -} - -// ============================================================================= - -/* Encoding & Decoding */ - -wstring EncodeUrl(const wstring& input, bool encode_unreserved) { - string str = ToANSI(input); - - wstring output; - output.reserve(input.size() * 2); - - for (size_t i = 0; i < str.length(); i++) { - if ((str[i] >= '0' && str[i] <= '9') || - (str[i] >= 'A' && str[i] <= 'Z') || - (str[i] >= 'a' && str[i] <= 'z') || - (!encode_unreserved && - (str[i] == '-' || str[i] == '.' || - str[i] == '_' || str[i] == '~'))) { - output.append(1, static_cast(str[i])); - } else { - static const wchar_t* digits = L"0123456789ABCDEF"; - output.append(L"%"); - output.append(&digits[(str[i] >> 4) & 0x0F], 1); - output.append(&digits[str[i] & 0x0F], 1); - } - } - - return output; -} - -void DecodeHtmlEntities(wstring& str) { - static std::map html_entities; - - // Build entity map - // Source: http://www.w3.org/TR/html4/sgml/entities.html - if (html_entities.empty()) { - // ISO 8859-1 characters - html_entities[L"nbsp"] = L'\u00A0'; - html_entities[L"iexcl"] = L'\u00A1'; - html_entities[L"cent"] = L'\u00A2'; - html_entities[L"pound"] = L'\u00A3'; - html_entities[L"curren"] = L'\u00A4'; - html_entities[L"yen"] = L'\u00A5'; - html_entities[L"brvbar"] = L'\u00A6'; - html_entities[L"sect"] = L'\u00A7'; - html_entities[L"uml"] = L'\u00A8'; - html_entities[L"copy"] = L'\u00A9'; - html_entities[L"ordf"] = L'\u00AA'; - html_entities[L"laquo"] = L'\u00AB'; - html_entities[L"not"] = L'\u00AC'; - html_entities[L"shy"] = L'\u00AD'; - html_entities[L"reg"] = L'\u00AE'; - html_entities[L"macr"] = L'\u00AF'; - html_entities[L"deg"] = L'\u00B0'; - html_entities[L"plusmn"] = L'\u00B1'; - html_entities[L"sup2"] = L'\u00B2'; - html_entities[L"sup3"] = L'\u00B3'; - html_entities[L"acute"] = L'\u00B4'; - html_entities[L"micro"] = L'\u00B5'; - html_entities[L"para"] = L'\u00B6'; - html_entities[L"middot"] = L'\u00B7'; - html_entities[L"cedil"] = L'\u00B8'; - html_entities[L"sup1"] = L'\u00B9'; - html_entities[L"ordm"] = L'\u00BA'; - html_entities[L"raquo"] = L'\u00BB'; - html_entities[L"frac14"] = L'\u00BC'; - html_entities[L"frac12"] = L'\u00BD'; - html_entities[L"frac34"] = L'\u00BE'; - html_entities[L"iquest"] = L'\u00BF'; - html_entities[L"Agrave"] = L'\u00C0'; - html_entities[L"Aacute"] = L'\u00C1'; - html_entities[L"Acirc"] = L'\u00C2'; - html_entities[L"Atilde"] = L'\u00C3'; - html_entities[L"Auml"] = L'\u00C4'; - html_entities[L"Aring"] = L'\u00C5'; - html_entities[L"AElig"] = L'\u00C6'; - html_entities[L"Ccedil"] = L'\u00C7'; - html_entities[L"Egrave"] = L'\u00C8'; - html_entities[L"Eacute"] = L'\u00C9'; - html_entities[L"Ecirc"] = L'\u00CA'; - html_entities[L"Euml"] = L'\u00CB'; - html_entities[L"Igrave"] = L'\u00CC'; - html_entities[L"Iacute"] = L'\u00CD'; - html_entities[L"Icirc"] = L'\u00CE'; - html_entities[L"Iuml"] = L'\u00CF'; - html_entities[L"ETH"] = L'\u00D0'; - html_entities[L"Ntilde"] = L'\u00D1'; - html_entities[L"Ograve"] = L'\u00D2'; - html_entities[L"Oacute"] = L'\u00D3'; - html_entities[L"Ocirc"] = L'\u00D4'; - html_entities[L"Otilde"] = L'\u00D5'; - html_entities[L"Ouml"] = L'\u00D6'; - html_entities[L"times"] = L'\u00D7'; - html_entities[L"Oslash"] = L'\u00D8'; - html_entities[L"Ugrave"] = L'\u00D9'; - html_entities[L"Uacute"] = L'\u00DA'; - html_entities[L"Ucirc"] = L'\u00DB'; - html_entities[L"Uuml"] = L'\u00DC'; - html_entities[L"Yacute"] = L'\u00DD'; - html_entities[L"THORN"] = L'\u00DE'; - html_entities[L"szlig"] = L'\u00DF'; - html_entities[L"agrave"] = L'\u00E0'; - html_entities[L"aacute"] = L'\u00E1'; - html_entities[L"acirc"] = L'\u00E2'; - html_entities[L"atilde"] = L'\u00E3'; - html_entities[L"auml"] = L'\u00E4'; - html_entities[L"aring"] = L'\u00E5'; - html_entities[L"aelig"] = L'\u00E6'; - html_entities[L"ccedil"] = L'\u00E7'; - html_entities[L"egrave"] = L'\u00E8'; - html_entities[L"eacute"] = L'\u00E9'; - html_entities[L"ecirc"] = L'\u00EA'; - html_entities[L"euml"] = L'\u00EB'; - html_entities[L"igrave"] = L'\u00EC'; - html_entities[L"iacute"] = L'\u00ED'; - html_entities[L"icirc"] = L'\u00EE'; - html_entities[L"iuml"] = L'\u00EF'; - html_entities[L"eth"] = L'\u00F0'; - html_entities[L"ntilde"] = L'\u00F1'; - html_entities[L"ograve"] = L'\u00F2'; - html_entities[L"oacute"] = L'\u00F3'; - html_entities[L"ocirc"] = L'\u00F4'; - html_entities[L"otilde"] = L'\u00F5'; - html_entities[L"ouml"] = L'\u00F6'; - html_entities[L"divide"] = L'\u00F7'; - html_entities[L"oslash"] = L'\u00F8'; - html_entities[L"ugrave"] = L'\u00F9'; - html_entities[L"uacute"] = L'\u00FA'; - html_entities[L"ucirc"] = L'\u00FB'; - html_entities[L"uuml"] = L'\u00FC'; - html_entities[L"yacute"] = L'\u00FD'; - html_entities[L"thorn"] = L'\u00FE'; - html_entities[L"yuml"] = L'\u00FF'; - // Symbols, mathematical symbols, and Greek letters - // Latin Extended-B - html_entities[L"fnof"] = L'\u0192'; - // Greek - html_entities[L"Alpha"] = L'\u0391'; - html_entities[L"Beta"] = L'\u0392'; - html_entities[L"Gamma"] = L'\u0393'; - html_entities[L"Delta"] = L'\u0394'; - html_entities[L"Epsilon"] = L'\u0395'; - html_entities[L"Zeta"] = L'\u0396'; - html_entities[L"Eta"] = L'\u0397'; - html_entities[L"Theta"] = L'\u0398'; - html_entities[L"Iota"] = L'\u0399'; - html_entities[L"Kappa"] = L'\u039A'; - html_entities[L"Lambda"] = L'\u039B'; - html_entities[L"Mu"] = L'\u039C'; - html_entities[L"Nu"] = L'\u039D'; - html_entities[L"Xi"] = L'\u039E'; - html_entities[L"Omicron"] = L'\u039F'; - html_entities[L"Pi"] = L'\u03A0'; - html_entities[L"Rho"] = L'\u03A1'; - html_entities[L"Sigma"] = L'\u03A3'; - html_entities[L"Tau"] = L'\u03A4'; - html_entities[L"Upsilon"] = L'\u03A5'; - html_entities[L"Phi"] = L'\u03A6'; - html_entities[L"Chi"] = L'\u03A7'; - html_entities[L"Psi"] = L'\u03A8'; - html_entities[L"Omega"] = L'\u03A9'; - html_entities[L"alpha"] = L'\u03B1'; - html_entities[L"beta"] = L'\u03B2'; - html_entities[L"gamma"] = L'\u03B3'; - html_entities[L"delta"] = L'\u03B4'; - html_entities[L"epsilon"] = L'\u03B5'; - html_entities[L"zeta"] = L'\u03B6'; - html_entities[L"eta"] = L'\u03B7'; - html_entities[L"theta"] = L'\u03B8'; - html_entities[L"iota"] = L'\u03B9'; - html_entities[L"kappa"] = L'\u03BA'; - html_entities[L"lambda"] = L'\u03BB'; - html_entities[L"mu"] = L'\u03BC'; - html_entities[L"nu"] = L'\u03BD'; - html_entities[L"xi"] = L'\u03BE'; - html_entities[L"omicron"] = L'\u03BF'; - html_entities[L"pi"] = L'\u03C0'; - html_entities[L"rho"] = L'\u03C1'; - html_entities[L"sigmaf"] = L'\u03C2'; - html_entities[L"sigma"] = L'\u03C3'; - html_entities[L"tau"] = L'\u03C4'; - html_entities[L"upsilon"] = L'\u03C5'; - html_entities[L"phi"] = L'\u03C6'; - html_entities[L"chi"] = L'\u03C7'; - html_entities[L"psi"] = L'\u03C8'; - html_entities[L"omega"] = L'\u03C9'; - html_entities[L"thetasym"] = L'\u03D1'; - html_entities[L"upsih"] = L'\u03D2'; - html_entities[L"piv"] = L'\u03D6'; - // General Punctuation - html_entities[L"bull"] = L'\u2022'; - html_entities[L"hellip"] = L'\u2026'; - html_entities[L"prime"] = L'\u2032'; - html_entities[L"Prime"] = L'\u2033'; - html_entities[L"oline"] = L'\u203E'; - html_entities[L"frasl"] = L'\u2044'; - // Letterlike Symbols - html_entities[L"weierp"] = L'\u2118'; - html_entities[L"image"] = L'\u2111'; - html_entities[L"real"] = L'\u211C'; - html_entities[L"trade"] = L'\u2122'; - html_entities[L"alefsym"] = L'\u2135'; - // Arrows - html_entities[L"larr"] = L'\u2190'; - html_entities[L"uarr"] = L'\u2191'; - html_entities[L"rarr"] = L'\u2192'; - html_entities[L"darr"] = L'\u2193'; - html_entities[L"harr"] = L'\u2194'; - html_entities[L"crarr"] = L'\u21B5'; - html_entities[L"lArr"] = L'\u21D0'; - html_entities[L"uArr"] = L'\u21D1'; - html_entities[L"rArr"] = L'\u21D2'; - html_entities[L"dArr"] = L'\u21D3'; - html_entities[L"hArr"] = L'\u21D4'; - // Mathematical Operators - html_entities[L"forall"] = L'\u2200'; - html_entities[L"part"] = L'\u2202'; - html_entities[L"exist"] = L'\u2203'; - html_entities[L"empty"] = L'\u2205'; - html_entities[L"nabla"] = L'\u2207'; - html_entities[L"isin"] = L'\u2208'; - html_entities[L"notin"] = L'\u2209'; - html_entities[L"ni"] = L'\u220B'; - html_entities[L"prod"] = L'\u220F'; - html_entities[L"sum"] = L'\u2211'; - html_entities[L"minus"] = L'\u2212'; - html_entities[L"lowast"] = L'\u2217'; - html_entities[L"radic"] = L'\u221A'; - html_entities[L"prop"] = L'\u221D'; - html_entities[L"infin"] = L'\u221E'; - html_entities[L"ang"] = L'\u2220'; - html_entities[L"and"] = L'\u2227'; - html_entities[L"or"] = L'\u2228'; - html_entities[L"cap"] = L'\u2229'; - html_entities[L"cup"] = L'\u222A'; - html_entities[L"int"] = L'\u222B'; - html_entities[L"there4"] = L'\u2234'; - html_entities[L"sim"] = L'\u223C'; - html_entities[L"cong"] = L'\u2245'; - html_entities[L"asymp"] = L'\u2248'; - html_entities[L"ne"] = L'\u2260'; - html_entities[L"equiv"] = L'\u2261'; - html_entities[L"le"] = L'\u2264'; - html_entities[L"ge"] = L'\u2265'; - html_entities[L"sub"] = L'\u2282'; - html_entities[L"sup"] = L'\u2283'; - html_entities[L"nsub"] = L'\u2284'; - html_entities[L"sube"] = L'\u2286'; - html_entities[L"supe"] = L'\u2287'; - html_entities[L"oplus"] = L'\u2295'; - html_entities[L"otimes"] = L'\u2297'; - html_entities[L"perp"] = L'\u22A5'; - html_entities[L"sdot"] = L'\u22C5'; - // Miscellaneous Technical - html_entities[L"lceil"] = L'\u2308'; - html_entities[L"rceil"] = L'\u2309'; - html_entities[L"lfloor"] = L'\u230A'; - html_entities[L"rfloor"] = L'\u230B'; - html_entities[L"lang"] = L'\u2329'; - html_entities[L"rang"] = L'\u232A'; - // Geometric Shapes - html_entities[L"loz"] = L'\u25CA'; - // Miscellaneous Symbols - html_entities[L"spades"] = L'\u2660'; - html_entities[L"clubs"] = L'\u2663'; - html_entities[L"hearts"] = L'\u2665'; - html_entities[L"diams"] = L'\u2666'; - // Markup-significant and internationalization characters - // C0 Controls and Basic Latin - html_entities[L"quot"] = L'\"'; - html_entities[L"amp"] = L'&'; - html_entities[L"lt"] = L'<'; - html_entities[L"gt"] = L'>'; - // Latin Extended-A - html_entities[L"OElig"] = L'\u0152'; - html_entities[L"oelig"] = L'\u0153'; - html_entities[L"Scaron"] = L'\u0160'; - html_entities[L"scaron"] = L'\u0161'; - html_entities[L"Yuml"] = L'\u0178'; - // Spacing Modifier Letters - html_entities[L"circ"] = L'\u02C6'; - html_entities[L"tilde"] = L'\u02DC'; - // General Punctuation - html_entities[L"ensp"] = L'\u2002'; - html_entities[L"emsp"] = L'\u2003'; - html_entities[L"thinsp"] = L'\u2009'; - html_entities[L"zwnj"] = L'\u200C'; - html_entities[L"zwj"] = L'\u200D'; - html_entities[L"lrm"] = L'\u200E'; - html_entities[L"rlm"] = L'\u200F'; - html_entities[L"ndash"] = L'\u2013'; - html_entities[L"mdash"] = L'\u2014'; - html_entities[L"lsquo"] = L'\u2018'; - html_entities[L"rsquo"] = L'\u2019'; - html_entities[L"sbquo"] = L'\u201A'; - html_entities[L"ldquo"] = L'\u201C'; - html_entities[L"rdquo"] = L'\u201D'; - html_entities[L"bdquo"] = L'\u201E'; - html_entities[L"dagger"] = L'\u2020'; - html_entities[L"Dagger"] = L'\u2021'; - html_entities[L"permil"] = L'\u2030'; - html_entities[L"lsaquo"] = L'\u2039'; - html_entities[L"rsaquo"] = L'\u203A'; - html_entities[L"euro"] = L'\u20AC'; - } - - if (InStr(str, L"&") == -1) return; - - size_t pos = 0, reference_pos = 0; - unsigned int character_value = -1; - - for (size_t i = 0; i < str.size(); i++) { - if (str.at(i) == L'&') { - reference_pos = i; - character_value = -1; - if (++i == str.size()) return; - - // Numeric character references - if (str.at(i) == L'#') { - if (++i == str.size()) return; - // Hexadecimal (&#xhhhh;) - if (str.at(i) == L'x') { - if (++i == str.size()) return; - pos = i; - while (i < str.size() && IsHex(str.at(i))) i++; - if (i > pos && i < str.size() && str.at(i) == L';') { - character_value = wcstoul(str.substr(pos, i - pos).c_str(), nullptr, 16); - } - // Decimal (&#nnnn;) - } else { - pos = i; - while (i < str.size() && IsNumeric(str.at(i))) i++; - if (i > pos && i < str.size() && str.at(i) == L';') { - character_value = ToInt(str.substr(pos, i - pos)); - } - } - - // Character entity references - } else { - pos = i; - while (i < str.size() && IsAlphanumeric(str.at(i))) i++; - if (i > pos && i < str.size() && str.at(i) == L';') { - size_t length = i - pos; - if (length > 1 && length < 10) { - wstring entity_name = str.substr(pos, length); - if (html_entities.find(entity_name) != html_entities.end()) { - character_value = html_entities[entity_name]; - } - } - } - } - - if (character_value <= 0xFFFD) { - str.replace(reference_pos, i - reference_pos + 1, - wstring(1, static_cast(character_value))); - i = reference_pos - 1; - } else { - i = reference_pos; - } - } - } -} - -void StripHtmlTags(wstring& str) { - int index_begin, index_end; - do { - index_begin = InStr(str, L"<", 0); - if (index_begin > -1) { - index_end = InStr(str, L">", index_begin); - if (index_end > -1) { - str.erase(index_begin, index_end - index_begin + 1); - } else { - break; - } - } - } while (index_begin > -1); -} - -// ============================================================================= - -/* Trimming */ - -wstring LimitText(const wstring& str, unsigned int limit, const wstring& tail) { - if (str.length() > limit) { - wstring limit_str = str.substr(0, limit); - if (!tail.empty() && limit_str.length() > tail.length()) { - limit_str.replace(limit_str.length() - tail.length(), tail.length(), tail); - } - return limit_str; - } else { - return str; - } -} - -void Trim(wstring& str, const wchar_t trim_chars[], bool trim_left, bool trim_right) { - if (str.empty()) return; - const size_t index_begin = trim_left ? str.find_first_not_of(trim_chars) : 0; - const size_t index_end = trim_right ? str.find_last_not_of(trim_chars) : str.length() - 1; - if (index_begin == wstring::npos || index_end == wstring::npos) { - str.clear(); - return; - } - if (trim_right) str.erase(index_end + 1, str.length() - index_end + 1); - if (trim_left) str.erase(0, index_begin); -} - -void TrimLeft(wstring& str, const wchar_t trim_chars[]) { - Trim(str, trim_chars, true, false); -} - -void TrimRight(wstring& str, const wchar_t trim_chars[]) { - Trim(str, trim_chars, false, true); -} - -// ============================================================================= - -/* File and folder related */ - -void AddTrailingSlash(wstring& str) { - if (str.length() > 0 && str[str.length() - 1] != '\\') - str += L"\\"; -} - -wstring AddTrailingSlash(const wstring& str) { - if (str.length() > 0 && str[str.length() - 1] != '\\') { - return str + L"\\"; - } else { - return str; - } -} - -bool CheckFileExtension(wstring extension, const vector& extension_list) { - if (extension.empty() || extension_list.empty()) return false; - for (size_t i = 0; i < extension.length(); i++) { - extension[i] = toupper(extension[i]); - } - for (size_t i = 0; i < extension_list.size(); i++) { - if (extension == extension_list[i]) return true; - } - return false; -} - -wstring GetFileExtension(const wstring& str) { - return str.substr(str.find_last_of(L".") + 1); -} - -wstring GetFileWithoutExtension(wstring str) { - size_t pos = str.find_last_of(L"."); - if (pos != wstring::npos) str.resize(pos); - return str; -} - -wstring GetFileName(const wstring& str) { - return str.substr(str.find_last_of(L"/\\") + 1); -} - -wstring GetPathOnly(const wstring& str) { - return str.substr(0, str.find_last_of(L"/\\") + 1); -} - -bool ValidateFileExtension(const wstring& extension, unsigned int max_length) { - if (max_length > 0 && extension.length() > max_length) return false; - if (!IsAlphanumeric(extension)) return false; - return true; -} - -// ============================================================================= - -/* Other */ - -void AppendString(wstring& str0, const wstring& str1, const wstring& str2) { - if (str1.empty()) return; - if (!str0.empty()) str0.append(str2); - str0.append(str1); -} - -wstring PadChar(wstring str, const wchar_t ch, const size_t len) { - if (len > str.length()) - str.insert(0, len - str.length(), ch); - return str; -} - -wstring PushString(const wstring& str1, const wstring& str2) { - if (str2.empty()) { - return L""; - } else { - return str1 + str2; - } -} - -void ReadStringFromResource(LPCWSTR name, LPCWSTR type, wstring& output) { - HRSRC hResInfo = FindResource(nullptr, name, type); - HGLOBAL hResHandle = LoadResource(nullptr, hResInfo); - DWORD dwSize = SizeofResource(nullptr, hResInfo); - - const char* lpData = static_cast(LockResource(hResHandle)); - string temp(lpData, dwSize); - output = ToUTF8(temp); - - FreeResource(hResInfo); -} - -void ReadStringTable(UINT uID, wstring& str) { - wchar_t buffer[2048]; - LoadString(g_hInstance, uID, buffer, 2048); - str.append(buffer); -} - -// ============================================================================= - -// Returns the most common non-alphanumeric character in a string that is included in the table. -// Characters in the table are listed by their precedence. - -const wchar_t* COMMON_CHAR_TABLE = L",_ -+;.&|~"; - -int GetCommonCharIndex(wchar_t c) { - for (int i = 0; i < 10; i++) - if (COMMON_CHAR_TABLE[i] == c) - return i; - return -1; -} - -wchar_t GetMostCommonCharacter(const wstring& str) { - vector> char_map; - int char_index = -1; - - for (size_t i = 0; i < str.length(); i++) { - if (!IsAlphanumeric(str[i])) { - char_index = GetCommonCharIndex(str[i]); - if (char_index == -1) continue; - for (auto it = char_map.begin(); it != char_map.end(); ++it) { - if (it->first == str[i]) { - it->second++; - char_index = -1; - break; - } - } - if (char_index > -1) { - char_map.push_back(std::make_pair(str[i], 1)); - } - } - } - - char_index = 0; - for (auto it = char_map.begin(); it != char_map.end(); ++it) { - if (it->second * 1.8f >= char_map.at(char_index).second && - GetCommonCharIndex(it->first) < GetCommonCharIndex(char_map.at(char_index).first)) { - char_index = it - char_map.begin(); - } - } - - return char_map.empty() ? '\0' : char_map.at(char_index).first; -} \ No newline at end of file diff --git a/string.h b/string.h deleted file mode 100644 index 0df062f57..000000000 --- a/string.h +++ /dev/null @@ -1,95 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef STRING_H -#define STRING_H - -#include "std.h" - -// ============================================================================= - -void AddTrailingSlash(wstring& str); -wstring AddTrailingSlash(const wstring& str); -void AppendString(wstring& str0, const wstring& str1, const wstring& str2 = L", "); -wstring CharLeft(const wstring& str, int length); -wstring CharRight(const wstring& str, int length); -bool CheckFileExtension(wstring extension, const vector& extension_list); -int CompareStrings(const wstring& str1, const wstring& str2, bool case_insensitive = true, size_t max_count = MAX_PATH); -void DecodeHtmlEntities(wstring& str); -wstring EncodeUrl(const wstring& str, bool encode_unreserved = false); -bool EndsWith(const wstring& str, const wstring& search); -size_t LongestCommonSubsequenceLength(const wstring& str1, const wstring& str2); -size_t LongestCommonSubstringLength(const wstring& str1, const wstring& str2); -size_t LevenshteinDistance(const wstring& str1, const wstring& str2); -void Erase(wstring& str1, const wstring& str2, bool case_insensitive = false); -void EraseChars(wstring& str, const wchar_t chars[]); -void EraseLeft(wstring& str1, const wstring str2, bool case_insensitive = false); -void ErasePunctuation(wstring& str, bool keep_trailing = false); -void EraseRight(wstring& str1, const wstring str2, bool case_insensitive = false); -void RemoveEmptyStrings(vector& input); -wstring GetFileExtension(const wstring& str); -wstring GetFileName(const wstring& str); -wstring GetFileWithoutExtension(wstring str); -wchar_t GetMostCommonCharacter(const wstring& str); -wstring GetPathOnly(const wstring& str); -int InStr(const wstring& str1, const wstring str2, int pos = 0, bool case_insensitive = false); -wstring InStr(const wstring& str1, const wstring& str2_left, const wstring& str2_right); -int InStrRev(const wstring& str1, const wstring str2, int pos); -int InStrChars(const wstring& str1, const wstring str2, int pos); -int InStrCharsRev(const wstring& str1, const wstring str2, int pos); -bool IsAlphanumeric(const wchar_t c); -bool IsAlphanumeric(const wstring& str); -inline bool IsCharsEqual(const wchar_t c1, const wchar_t c2); -bool IsEqual(const wstring& str1, const wstring& str2); -bool IsHex(const wchar_t c); -bool IsHex(const wstring& str); -bool IsNumeric(const wchar_t c); -bool IsNumeric(const wstring& str); -bool IsWhitespace(const wchar_t c); -wstring LimitText(const wstring& str, unsigned int limit, const wstring& tail = L"..."); -wstring PadChar(wstring str, const wchar_t ch, const size_t len); -wstring PushString(const wstring& str1, const wstring& str2); -void ReadStringFromResource(LPCWSTR name, LPCWSTR type, wstring& output); -void ReadStringTable(UINT uID, wstring& str); -void Replace(wstring& str1, wstring str2, wstring replace_with, bool replace_all = false, bool case_insensitive = false); -void ReplaceChar(wstring& str, const wchar_t c, const wchar_t replace_with); -void ReplaceChars(wstring& str, const wchar_t chars[], const wstring replace_with); -wstring Join(const vector& join_vector, const wstring& separator); -void Split(const wstring& str, const wstring& separator, std::vector& split_vector); -bool StartsWith(const wstring& str, const wstring& search); -void StripHtmlTags(wstring& str); -wstring SubStr(const wstring& str, const wstring& sub_begin, const wstring& sub_end); -size_t Tokenize(const wstring& str, const wstring& delimiters, vector& tokens); -const char* ToANSI(const wstring& str, UINT code_page = CP_UTF8); -wstring ToUTF8(const string& str, UINT code_page = CP_UTF8); -void ToLower(wstring& str, bool use_locale = false); -wstring ToLower_Copy(wstring str, bool use_locale = false); -void ToUpper(wstring& str, bool use_locale = false); -wstring ToUpper_Copy(wstring str, bool use_locale = false); -int ToInt(const wstring& str); -wstring ToWstr(const INT& value); -wstring ToWstr(const ULONG& value); -wstring ToWstr(const INT64& value); -wstring ToWstr(const UINT64& value); -wstring ToWstr(const double& value, int count = 16); -void Trim(wstring& str, const wchar_t trim_chars[] = L" ", bool trim_left = true, bool trim_right = true); -void TrimLeft(wstring& str, const wchar_t trim_chars[] = L" "); -void TrimRight(wstring& str, const wchar_t trim_chars[] = L" "); -bool ValidateFileExtension(const wstring& extension, unsigned int max_length = 0); - -#endif // STRING_H diff --git a/taiga.cpp b/taiga.cpp deleted file mode 100644 index fd994060e..000000000 --- a/taiga.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "taiga.h" - -#include "anime_db.h" -#include "announce.h" -#include "api.h" -#include "common.h" -#include "gfx.h" -#include "history.h" -#include "logger.h" -#include "media.h" -#include "monitor.h" -#include "myanimelist.h" -#include "process.h" -#include "recognition.h" -#include "resource.h" -#include "settings.h" -#include "string.h" -#include "theme.h" -#include "version.h" - -#include "dlg/dlg_update.h" - -#include "win32/win_taskbar.h" -#include "win32/win_taskdialog.h" - -HINSTANCE g_hInstance; -HWND g_hMain; -class Taiga Taiga; - -// ============================================================================= - -Taiga::Taiga() - : logged_in(false), - current_tip_type(TIPTYPE_DEFAULT), - play_status(PLAYSTATUS_STOPPED), - ticker_media(0), - ticker_memory(0), - ticker_new_episodes(0), - ticker_queue(0) { - SetVersionInfo(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION); -} - -Taiga::~Taiga() { - OleUninitialize(); -} - -// ============================================================================= - -BOOL Taiga::InitInstance() { - // Check another instance - if (CheckInstance(L"Taiga-33d5a63c-de90-432f-9a8b-f6f733dab258", L"TaigaMainW")) - return FALSE; - g_hInstance = GetInstanceHandle(); - - // Initialize - InitCommonControls(ICC_STANDARD_CLASSES); - OleInitialize(NULL); - - // Initialize logger -#ifdef _DEBUG - Logger.SetOutputPath(AddTrailingSlash(GetCurrentDirectory()) + L"Taiga_debug.log"); - Logger.SetSeverityLevel(LevelDebug); -#else - Logger.SetOutputPath(AddTrailingSlash(GetPathOnly(GetModulePath())) + L"Taiga.log"); - Logger.SetSeverityLevel(LevelWarning); -#endif - - // Load data - LoadData(); - - // Create API windows - Skype.Create(); - TaigaApi.Create(); - - if (Settings.Program.StartUp.check_new_version) { - // Create update dialog - ExecuteAction(L"CheckUpdates"); - } else { - // Create main dialog - ExecuteAction(L"MainDialog"); - } - - return TRUE; -} - -void Taiga::Uninitialize() { - // Announce - if (play_status == PLAYSTATUS_PLAYING) { - play_status = PLAYSTATUS_STOPPED; - Announcer.Do(ANNOUNCE_TO_HTTP); - } - Announcer.Clear(ANNOUNCE_TO_MESSENGER | ANNOUNCE_TO_SKYPE); - - // Cleanup - Clients.service.list.Cleanup(); - Taskbar.Destroy(); - TaskbarList.Release(); - - // Save - Settings.Save(); - AnimeDatabase.SaveDatabase(); - Aggregator.SaveArchive(); - - // Exit - PostQuitMessage(); -} - -// ============================================================================= - -wstring Taiga::GetDataPath() { - // Return current working directory in debug mode -#ifdef _DEBUG - return AddTrailingSlash(GetCurrentDirectory()) + L"data\\"; -#endif - - // Return current path in portable mode -#ifdef PORTABLE - return AddTrailingSlash(GetPathOnly(GetModulePath())) + L"data\\"; -#endif - - // Return %AppData% folder - WCHAR buffer[MAX_PATH]; - if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, - NULL, SHGFP_TYPE_CURRENT, buffer))) { - return AddTrailingSlash(buffer) + APP_NAME + L"\\"; - } - - return L""; -} - -void Taiga::LoadData() { - MediaPlayers.Load(); - - if (Settings.Load()) - Settings.HandleCompatibility(); - - UI.Load(Settings.Program.General.theme); - UI.LoadImages(); - - AnimeDatabase.LoadDatabase(); - AnimeDatabase.LoadList(); - AnimeDatabase.ClearInvalidItems(); - - History.Load(); -} \ No newline at end of file diff --git a/theme.cpp b/theme.cpp deleted file mode 100644 index 435e2378c..000000000 --- a/theme.cpp +++ /dev/null @@ -1,259 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "theme.h" - -#include "gfx.h" -#include "string.h" -#include "taiga.h" -#include "xml.h" - -Theme UI; - -// ============================================================================= - -Theme::Theme() { - // Create image lists - ImgList16.Create(16, 16); - ImgList24.Create(24, 24); - Menus.SetImageList(ImgList16.GetHandle()); -} - -bool Theme::Load(const wstring& name) { - // Initialize - folder_ = Taiga.GetDataPath() + L"theme\\" + name + L"\\"; - file_ = folder_ + L"Theme.xml"; - - // Load XML file - xml_document doc; - xml_parse_result result = doc.load_file(file_.c_str()); - if (result.status != status_ok) { - MessageBox(NULL, L"Could not read theme file.", file_.c_str(), MB_OK | MB_ICONERROR); - return false; - } - - // Read theme - xml_node theme = doc.child(L"theme"); - xml_node icons16 = theme.child(L"icons").child(L"set_16px"); - xml_node icons24 = theme.child(L"icons").child(L"set_24px"); - xml_node image = theme.child(L"list").child(L"background").child(L"image"); - xml_node progress = theme.child(L"list").child(L"progress"); - - // Read icons - icons16_.clear(); - icons24_.clear(); - for (xml_node icon = icons16.child(L"icon"); icon; icon = icon.next_sibling(L"icon")) - icons16_.push_back(icon.attribute(L"name").value()); - for (xml_node icon = icons24.child(L"icon"); icon; icon = icon.next_sibling(L"icon")) - icons24_.push_back(icon.attribute(L"name").value()); - - // Read list - if (list_background.bitmap) { - DeleteObject(list_background.bitmap); - list_background.bitmap = nullptr; - } - list_background.name = image.attribute(L"name").value(); - list_background.flags = image.attribute(L"flags").as_int(); - list_background.offset_x = image.attribute(L"offset_x").as_int(); - list_background.offset_y = image.attribute(L"offset_y").as_int(); - if (!list_background.name.empty()) { - wstring path = folder_ + list_background.name + L".png"; - Gdiplus::Bitmap bmp(path.c_str()); - bmp.GetHBITMAP(NULL, &list_background.bitmap); - } - #define READ_PROGRESS_DATA(x, name) \ - list_progress.x.type = progress.child(name).attribute(L"type").value(); \ - list_progress.x.value[0] = HexToARGB(progress.child(name).attribute(L"value_1").value()); \ - list_progress.x.value[1] = HexToARGB(progress.child(name).attribute(L"value_2").value()); \ - list_progress.x.value[2] = HexToARGB(progress.child(name).attribute(L"value_3").value()); - READ_PROGRESS_DATA(aired, L"aired"); - READ_PROGRESS_DATA(available, L"available"); - READ_PROGRESS_DATA(background, L"background"); - READ_PROGRESS_DATA(border, L"border"); - READ_PROGRESS_DATA(button, L"button"); - READ_PROGRESS_DATA(completed, L"completed"); - READ_PROGRESS_DATA(onhold, L"onhold"); - READ_PROGRESS_DATA(dropped, L"dropped"); - READ_PROGRESS_DATA(plantowatch, L"plantowatch"); - READ_PROGRESS_DATA(separator, L"separator"); - READ_PROGRESS_DATA(watching, L"watching"); - #undef READ_PROGRESS_DATA - - // Read menus - Menus.Menu.clear(); - wstring menu_resource; - ReadStringFromResource(L"IDR_MENU", L"DATA", menu_resource); - result = doc.load(menu_resource.data()); - xml_node menus = doc.child(L"menus"); - for (xml_node menu = menus.child(L"menu"); menu; menu = menu.next_sibling(L"menu")) { - Menus.Create(menu.attribute(L"name").value(), menu.attribute(L"type").value()); - for (xml_node item = menu.child(L"item"); item; item = item.next_sibling(L"item")) { - UI.Menus.Menu.back().CreateItem( - item.attribute(L"action").value(), - item.attribute(L"name").value(), - item.attribute(L"sub").value(), - item.attribute(L"checked").as_bool(), - item.attribute(L"default").as_bool(), - !item.attribute(L"disabled").as_bool(), - item.attribute(L"column").as_bool(), - item.attribute(L"radio").as_bool()); - } - } - - return true; -} - -bool Theme::LoadImages() { - // Clear image lists - ImgList16.Remove(-1); - ImgList24.Remove(-1); - - // Populate image lists - HBITMAP hBitmap; - for (size_t i = 0; i < ICONCOUNT_16PX && i < icons16_.size(); i++) { - hBitmap = GdiPlus.LoadImage(folder_ + L"16px\\" + icons16_.at(i) + L".png"); - ImgList16.AddBitmap(hBitmap, CLR_NONE); - DeleteObject(hBitmap); - } - for (size_t i = 0; i < ICONCOUNT_24PX && i < icons24_.size(); i++) { - hBitmap = GdiPlus.LoadImage(folder_ + L"24px\\" + icons24_.at(i) + L".png"); - ImgList24.AddBitmap(hBitmap, CLR_NONE); - DeleteObject(hBitmap); - } - - return true; -} - -// ============================================================================= - -Brush::Brush() - : brush_(nullptr) { -} - -Brush::Brush(HBRUSH brush) - : brush_(brush) { -} - -Brush::~Brush() { - Set(nullptr); -} - -HBRUSH Brush::Get() const { - return brush_; -} - -void Brush::Set(HBRUSH brush) { - if (brush_) - ::DeleteObject(brush_); - brush_ = brush; -} - -Brush::operator HBRUSH() const { - return brush_; -} - -void Theme::CreateBrushes() { - if (brush_background.Get()) - return; - - brush_background.Set(CreateSolidBrush(GetSysColor(COLOR_WINDOW))); -} - -// ============================================================================= - -Font::Font() - : font_(nullptr) { -} - -Font::Font(HFONT font) - : font_(font) { -} - -Font::~Font() { - Set(nullptr); -} - -HFONT Font::Get() const { - return font_; -} - -void Font::Set(HFONT font) { - if (font_) - ::DeleteObject(font_); - font_ = font; -} - -Font::operator HFONT() const { - return font_; -} - -void Theme::CreateFonts(HDC hdc) { - if (font_bold.Get() && font_header.Get()) - return; - - LOGFONT lFont = {0}; - lFont.lfCharSet = DEFAULT_CHARSET; - lFont.lfOutPrecision = OUT_STRING_PRECIS; - lFont.lfClipPrecision = CLIP_STROKE_PRECIS; - lFont.lfQuality = PROOF_QUALITY; - lFont.lfPitchAndFamily = VARIABLE_PITCH; - - // Bold font - lFont.lfHeight = -MulDiv(9, GetDeviceCaps(hdc, LOGPIXELSY), 72); - lFont.lfWeight = FW_BOLD; - lstrcpy(lFont.lfFaceName, L"Segoe UI"); - font_bold.Set(::CreateFontIndirect(&lFont)); - - // Header font - lFont.lfHeight = -MulDiv(12, GetDeviceCaps(hdc, LOGPIXELSY), 72); - lFont.lfWeight = FW_NORMAL; - lstrcpy(lFont.lfFaceName, L"Segoe UI"); - font_header.Set(::CreateFontIndirect(&lFont)); -} - -// ============================================================================= - -Theme::ListBackground::ListBackground() - : bitmap(nullptr) { -} - -Theme::ListBackground::~ListBackground() { - if (bitmap) { - ::DeleteObject(bitmap); - bitmap = nullptr; - } -} - -void Theme::ListProgress::Item::Draw(HDC hdc, const LPRECT rect) { - // Solid - if (type == L"solid") { - HBRUSH hbrSolid = CreateSolidBrush(value[0]); - FillRect(hdc, rect, hbrSolid); - DeleteObject(hbrSolid); - - // Gradient - } else if (type == L"gradient") { - GradientRect(hdc, rect, value[0], value[1], value[2] > 0); - - // Progress bar - } else if (type == L"progress") { - DrawProgressBar(hdc, rect, value[0], value[1], value[2]); - } -} \ No newline at end of file diff --git a/theme.h b/theme.h deleted file mode 100644 index 436a33f6a..000000000 --- a/theme.h +++ /dev/null @@ -1,178 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef THEME_H -#define THEME_H - -#include "std.h" -#include "win32/win_control.h" -#include "win32/win_menu.h" - -enum Icons16px { - ICON16_GREEN, - ICON16_BLUE, - ICON16_RED, - ICON16_GRAY, - ICON16_PLAY, - ICON16_SEARCH, - ICON16_FOLDER, - ICON16_APP_BLUE, - ICON16_APP_GRAY, - ICON16_REFRESH, - ICON16_DOWNLOAD, - ICON16_SETTINGS, - ICON16_CROSS, - ICON16_PLUS, - ICON16_MINUS, - ICON16_ARROW_UP, - ICON16_ARROW_DOWN, - ICON16_FUNNEL, - ICON16_FUNNEL_CROSS, - ICON16_FUNNEL_TICK, - ICON16_FUNNEL_PLUS, - ICON16_FUNNEL_PENCIL, - ICON16_CALENDAR, - ICON16_CATEGORY, - ICON16_SORT, - ICON16_BALLOON, - ICON16_CLOCK, - ICON16_HOME, - ICON16_DOCUMENT_A, - ICON16_CHART, - ICON16_FEED, - ICON16_DETAILS, - ICONCOUNT_16PX -}; - -enum Icons24px { - ICON24_SYNC, - ICON24_MAL, - ICON24_FOLDERS, - ICON24_TOOLS, - ICON24_SETTINGS, - ICON24_ABOUT, - ICON24_GLOBE, - ICON24_LIBRARY, - ICON24_APPLICATION, - ICON24_RECOGNITION, - ICON24_SHARING, - ICON24_FEED, - ICONCOUNT_24PX -}; - -namespace theme { -const COLORREF COLOR_DARKBLUE = RGB(46, 81, 162); -const COLORREF COLOR_GRAY = RGB(230, 230, 230); -const COLORREF COLOR_LIGHTBLUE = RGB(225, 231, 245); -const COLORREF COLOR_LIGHTGRAY = RGB(248, 248, 248); -const COLORREF COLOR_LIGHTGREEN = RGB(225, 245, 231); -const COLORREF COLOR_LIGHTRED = RGB(245, 225, 231); -const COLORREF COLOR_MAININSTRUCTION = RGB(0x00, 0x33, 0x99); -} - -// ============================================================================= - -class Brush { -public: - Brush(); - Brush(HBRUSH brush); - ~Brush(); - - HBRUSH Get() const; - void Set(HBRUSH brush); - - operator HBRUSH() const; - -private: - HBRUSH brush_; -}; - -class Font { -public: - Font(); - Font(HFONT font); - ~Font(); - - HFONT Get() const; - void Set(HFONT font); - - operator HFONT() const; - -private: - HFONT font_; -}; - -class Theme { -public: - Theme(); - ~Theme() {} - - void CreateBrushes(); - void CreateFonts(HDC hdc); - bool Load(const wstring& name); - bool LoadImages(); - -public: - win32::MenuList Menus; - - win32::ImageList ImgList16; - win32::ImageList ImgList24; - - Brush brush_background; - Font font_bold; - Font font_header; - - class ListBackground { - public: - ListBackground(); - ~ListBackground(); - wstring name; - DWORD flags; - int offset_x, offset_y; - HBITMAP bitmap; - } list_background; - - class ListProgress { - public: - class Item { - public: - void Draw(HDC hdc, const LPRECT rect); - COLORREF value[3]; - wstring type; - } - aired, - available, - background, - border, - button, - completed, - onhold, - dropped, - plantowatch, - separator, - watching; - } list_progress; - -private: - wstring file_, folder_; - vector icons16_, icons24_; -}; - -extern Theme UI; - -#endif // THEME_H \ No newline at end of file diff --git a/third_party/oauth/oauth.cpp b/third_party/oauth/oauth.cpp deleted file mode 100644 index 52dd5ba69..000000000 --- a/third_party/oauth/oauth.cpp +++ /dev/null @@ -1,293 +0,0 @@ -/* -** Copyright (c) 2010 Brook Miles -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -** THE SOFTWARE. -*/ - -#include -#include -#include -#include - -#include "../base64/base64.h" -#include "oauth.h" -#include "../../string.h" - -#define SIZEOF(x) (sizeof(x)/sizeof(*x)) - -using std::list; -using std::string; -using std::wstring; - -// ============================================================================= - -wstring COAuth::BuildHeader( - const wstring& url, - const wstring& http_method, - const OAuthParameters* post_parameters, - const wstring& oauth_token, - const wstring& oauth_token_secret, - const wstring& pin) -{ - // Build request parameters - OAuthParameters get_parameters = ParseQueryString(UrlGetQuery(url)); - - // Build signed OAuth parameters - OAuthParameters signed_parameters = - BuildSignedParameters(get_parameters, url, http_method, post_parameters, - oauth_token, oauth_token_secret, pin); - - // Build and return OAuth header - wstring oauth_header = L"Authorization: OAuth "; - for (OAuthParameters::const_iterator it = signed_parameters.begin(); - it != signed_parameters.end(); ++it) { - if (it != signed_parameters.begin()) oauth_header += L", "; - oauth_header += it->first + L"=\"" + it->second + L"\""; - } - oauth_header += L"\r\n"; - return oauth_header; -} - -OAuthParameters COAuth::ParseQueryString(const wstring& url) { - OAuthParameters parsed_parameters; - - vector parameters; - Split(url, L"&", parameters); - - for (size_t i = 0; i < parameters.size(); ++i) { - vector elements; - Split(parameters[i], L"=", elements); - if (elements.size() == 2) { - parsed_parameters[elements[0]] = elements[1]; - } - } - - return parsed_parameters; -} - -// ============================================================================= - -OAuthParameters COAuth::BuildSignedParameters( - const OAuthParameters& get_parameters, - const wstring& url, - const wstring& http_method, - const OAuthParameters* post_parameters, - const wstring& oauth_token, - const wstring& oauth_token_secret, - const wstring& pin) -{ - // Create OAuth parameters - OAuthParameters oauth_parameters; - oauth_parameters[L"oauth_callback"] = L"oob"; - oauth_parameters[L"oauth_consumer_key"] = ConsumerKey; - oauth_parameters[L"oauth_nonce"] = CreateNonce(); - oauth_parameters[L"oauth_signature_method"] = L"HMAC-SHA1"; - oauth_parameters[L"oauth_timestamp"] = CreateTimestamp(); - oauth_parameters[L"oauth_version"] = L"1.0"; - - // Add the request token - if (!oauth_token.empty()) { - oauth_parameters[L"oauth_token"] = oauth_token; - } - // Add the authorization PIN - if (!pin.empty()) { - oauth_parameters[L"oauth_verifier"] = pin; - } - - // Create a parameter list containing both OAuth and original parameters - OAuthParameters all_parameters = get_parameters; - if (CompareStrings(http_method, L"POST") == 0 && post_parameters) { - all_parameters.insert(post_parameters->begin(), post_parameters->end()); - } - all_parameters.insert(oauth_parameters.begin(), oauth_parameters.end()); - - // Prepare the signature base - wstring normal_url = NormalizeURL(url); - wstring sorted_parameters = SortParameters(all_parameters); - wstring signature_base = http_method + L"&" + EncodeUrl(normal_url) + L"&" + EncodeUrl(sorted_parameters); - - // Obtain a signature and add it to header parameters - wstring signature = CreateSignature(signature_base, oauth_token_secret); - oauth_parameters[L"oauth_signature"] = signature; - - return oauth_parameters; -} - -// ============================================================================= - -wstring COAuth::CreateNonce() { - wchar_t ALPHANUMERIC[] = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - wstring nonce; - for (int i = 0; i <= 16; ++i) { - nonce += ALPHANUMERIC[rand() % (SIZEOF(ALPHANUMERIC) - 1)]; - } - return nonce; -} - -wstring COAuth::CreateSignature(const wstring& signature_base, const wstring& oauth_token_secret) { - // Create a SHA-1 hash of signature - wstring key = EncodeUrl(ConsumerSecret) + L"&" + EncodeUrl(oauth_token_secret); - string hash = Crypt_HMACSHA1(ToANSI(key), ToANSI(signature_base)); - - // Encode signature in Base64 - Base64Coder coder; - coder.Encode((BYTE*)hash.c_str(), hash.size()); - wstring signature = ToUTF8(coder.EncodedMessage()); - - // Return URL-encoded signature - return EncodeUrl(signature); -} - -wstring COAuth::CreateTimestamp() { - __time64_t utcNow; - __time64_t ret = _time64(&utcNow); - wchar_t buf[100] = {'\0'}; - swprintf_s(buf, SIZEOF(buf), L"%I64u", utcNow); - return buf; -} - -wstring COAuth::NormalizeURL(const wstring& url) { - wchar_t scheme[1024 * 4] = {'\0'}; - wchar_t host[1024 * 4] = {'\0'}; - wchar_t path[1024 * 4] = {'\0'}; - - URL_COMPONENTS components = {sizeof(URL_COMPONENTS)}; - components.lpszScheme = scheme; - components.dwSchemeLength = SIZEOF(scheme); - components.lpszHostName = host; - components.dwHostNameLength = SIZEOF(host); - components.lpszUrlPath = path; - components.dwUrlPathLength = SIZEOF(path); - - wstring normal_url = url; - if (::WinHttpCrackUrl(url.c_str(), url.size(), 0, &components)) { - // Include port number if it is non-standard - wchar_t port[10] = {}; - if ((CompareStrings(scheme, L"http") == 0 && components.nPort != 80) || - (CompareStrings(scheme, L"https") == 0 && components.nPort != 443)) { - swprintf_s(port, SIZEOF(port), L":%u", components.nPort); - } - // Strip off ? and # elements in the path - wstring path_only = path; - wstring::size_type pos = path_only.find_first_of(L"#?"); - if (pos != wstring::npos) { - path_only = path_only.substr(0, pos); - } - // Build normal URL - normal_url = wstring(scheme) + L"://" + host + port + path_only; - } - return normal_url; -} - -wstring COAuth::SortParameters(const OAuthParameters& parameters) { - list sorted; - for (OAuthParameters::const_iterator it = parameters.begin(); it != parameters.end(); ++it) { - wstring param = it->first + L"=" + it->second; - sorted.push_back(param); - } - sorted.sort(); - - wstring params; - for (list::iterator it = sorted.begin(); it != sorted.end(); ++it) { - if (params.size() > 0) { - params += L"&"; - } - params += *it; - } - return params; -} - -wstring COAuth::UrlGetQuery(const wstring& url) { - wstring query; - wchar_t buf[1024 * 4] = {'\0'}; - - URL_COMPONENTS components = {sizeof(URL_COMPONENTS)}; - components.dwExtraInfoLength = SIZEOF(buf); - components.lpszExtraInfo = buf; - - if (::WinHttpCrackUrl(url.c_str(), url.size(), 0, &components)) { - query = components.lpszExtraInfo; - wstring::size_type q = query.find_first_of(L'?'); - if (q != wstring::npos) { - query = query.substr(q + 1); - } - wstring::size_type h = query.find_first_of(L'#'); - if (h != wstring::npos) { - query = query.substr(0, h); - } - } - - return query; -} - -// ============================================================================= - -// HMAC (Hashed Message Authentication Checksum) code is based on: -// http://msdn.microsoft.com/en-us/library/aa382379%28v=VS.85%29.aspx - -// Key creation is based on: -// http://mirror.leaseweb.com/NetBSD/NetBSD-release-5-0/src/dist/wpa/src/crypto/crypto_cryptoapi.c - -string COAuth::Crypt_HMACSHA1(const string& key_bytes, const string& data) { - string hash; - HCRYPTPROV hProv = NULL; - HCRYPTHASH hHash = NULL; - HCRYPTKEY hKey = NULL; - HCRYPTHASH hHmacHash = NULL; - PBYTE pbHash = NULL; - DWORD dwDataLen = 0; - HMAC_INFO HmacInfo; - ZeroMemory(&HmacInfo, sizeof(HmacInfo)); - HmacInfo.HashAlgid = CALG_SHA1; - - if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { - if (CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) { - if (CryptHashData(hHash, (BYTE*)key_bytes.c_str(), key_bytes.size(), 0)) { - struct { - BLOBHEADER hdr; - DWORD len; - BYTE key[1024]; - } key_blob; - key_blob.hdr.bType = PLAINTEXTKEYBLOB; - key_blob.hdr.bVersion = CUR_BLOB_VERSION; - key_blob.hdr.reserved = 0; - key_blob.hdr.aiKeyAlg = CALG_RC2; - key_blob.len = key_bytes.size(); - ZeroMemory(key_blob.key, sizeof(key_blob.key)); - CopyMemory(key_blob.key, key_bytes.c_str(), min(key_bytes.size(), SIZEOF(key_blob.key))); - if (CryptImportKey(hProv, (BYTE*)&key_blob, sizeof(key_blob), 0, CRYPT_IPSEC_HMAC_KEY, &hKey)) { - if (CryptCreateHash(hProv, CALG_HMAC, hKey, 0, &hHmacHash)) { - if (CryptSetHashParam(hHmacHash, HP_HMAC_INFO, (BYTE*)&HmacInfo, 0)) { - if (CryptHashData(hHmacHash, (BYTE*)data.c_str(), data.size(), 0)) { - if (CryptGetHashParam(hHmacHash, HP_HASHVAL, NULL, &dwDataLen, 0)) { - pbHash = (BYTE*)malloc(dwDataLen); - if (pbHash != NULL) { - if (CryptGetHashParam(hHmacHash, HP_HASHVAL, pbHash, &dwDataLen, 0)) { - for (DWORD i = 0 ; i < dwDataLen ; i++) { - hash.push_back((char)pbHash[i]); - } } } } } } } } } } } - - if (hHmacHash) CryptDestroyHash(hHmacHash); - if (hKey) CryptDestroyKey(hKey); - if (hHash) CryptDestroyHash(hHash); - if (hProv) CryptReleaseContext(hProv, 0); - if (pbHash) free(pbHash); - - return hash; -} \ No newline at end of file diff --git a/third_party/oauth/oauth.h b/third_party/oauth/oauth.h deleted file mode 100644 index 6830bc24e..000000000 --- a/third_party/oauth/oauth.h +++ /dev/null @@ -1,70 +0,0 @@ -/* -** Copyright (c) 2010 Brook Miles -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -** THE SOFTWARE. -*/ - -#ifndef OAUTH_H -#define OAUTH_H - -#include -#include - -using std::string; -using std::wstring; - -typedef std::map OAuthParameters; - -// ============================================================================= - -class COAuth { -public: - COAuth() {} - virtual ~COAuth() {} - - wstring ConsumerKey, ConsumerSecret; - - wstring BuildHeader( - const wstring& url, - const wstring& http_method, - const OAuthParameters* post_parameters = NULL, - const wstring& oauth_token = L"", - const wstring& oauth_token_secret = L"", - const wstring& pin = L""); - OAuthParameters ParseQueryString(const wstring& url); - -private: - OAuthParameters BuildSignedParameters( - const OAuthParameters& get_parameters, - const wstring& url, - const wstring& http_method, - const OAuthParameters* post_parameters, - const wstring& oauth_token, - const wstring& oauth_token_secret, - const wstring& pin); - wstring CreateNonce(); - wstring CreateSignature(const wstring& signature_base, const wstring& oauth_token_secret); - wstring CreateTimestamp(); - string Crypt_HMACSHA1(const string& key_bytes, const string& data); - wstring NormalizeURL(const wstring& url); - wstring SortParameters(const OAuthParameters& parameters); - wstring UrlGetQuery(const wstring& url); -}; - -#endif // OAUTH_H \ No newline at end of file diff --git a/tools/update/UpdateHelper.sln b/tools/update/UpdateHelper.sln deleted file mode 100644 index 51a8c8816..000000000 --- a/tools/update/UpdateHelper.sln +++ /dev/null @@ -1,20 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual C++ Express 2010 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UpdateHelper", "UpdateHelper.vcxproj", "{27F1B4FA-66D2-4853-BE45-BD0FC0630B7E}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {27F1B4FA-66D2-4853-BE45-BD0FC0630B7E}.Debug|Win32.ActiveCfg = Debug|Win32 - {27F1B4FA-66D2-4853-BE45-BD0FC0630B7E}.Debug|Win32.Build.0 = Debug|Win32 - {27F1B4FA-66D2-4853-BE45-BD0FC0630B7E}.Release|Win32.ActiveCfg = Release|Win32 - {27F1B4FA-66D2-4853-BE45-BD0FC0630B7E}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/tools/update/UpdateHelper.vcxproj b/tools/update/UpdateHelper.vcxproj deleted file mode 100644 index bbc068504..000000000 --- a/tools/update/UpdateHelper.vcxproj +++ /dev/null @@ -1,83 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - - {27F1B4FA-66D2-4853-BE45-BD0FC0630B7E} - Win32Proj - UpdateHelper - - - - Application - true - Unicode - - - Application - false - true - Unicode - - - - - - - - - - - - - true - - - false - - - - - - Level3 - Disabled - WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) - - - Windows - true - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) - MultiThreaded - - - Windows - true - true - true - - - - - - - - - \ No newline at end of file diff --git a/tools/update/UpdateHelper.vcxproj.filters b/tools/update/UpdateHelper.vcxproj.filters deleted file mode 100644 index 2a142563d..000000000 --- a/tools/update/UpdateHelper.vcxproj.filters +++ /dev/null @@ -1,22 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - \ No newline at end of file diff --git a/tools/update/main.cpp b/tools/update/main.cpp deleted file mode 100644 index 1aa3d52f4..000000000 --- a/tools/update/main.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* -** Copyright (c) 2011-2012, Eren Okka -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is -** furnished to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in -** all copies or substantial portions of the Software. -** -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -** THE SOFTWARE. -*/ - -#include - -int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) { - // argv[0]: Application path (not used) - // argv[1]: Old file - // argv[2]: New file - // argv[3]: Process ID (optional) - int argc = 0; - LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); - if (argc < 3 || argv == nullptr) { - MessageBoxW(nullptr, L"Usage example:\n" - L"UpdateHelper.exe App.exe App.exe.new [Process ID]", - nullptr, MB_ICONERROR | MB_OK); - return 1; - } - - // Wait for the application to close - DWORD dwProcessId = argc > 3 ? _wtoi(argv[3]) : 0; - if (dwProcessId) { - HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProcessId); - if (hProcess) { - DWORD dwExitCode; - // Wait until the process is no longer available - while (GetExitCodeProcess(hProcess, &dwExitCode) == STILL_ACTIVE) { - // TODO: Timeout - Sleep(1000); - } - CloseHandle(hProcess); - } - } - - // Take a backup of the old file (App.exe -> App.exe.bak) - WCHAR backup[MAX_PATH] = {'\0'}; - wcscpy_s(backup, MAX_PATH, argv[1]); - wcscat_s(backup, MAX_PATH, L".bak"); - DWORD dwFlags = MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH; - MoveFileExW(argv[1], backup, dwFlags); - - // Rename the new file (App.exe.new -> App.exe) - if (MoveFileExW(argv[2], argv[1], dwFlags)) { - // Delete the backup (App.exe.bak) - DeleteFileW(backup); - // Execute the new file - ShellExecuteW(nullptr, L"open", argv[1], nullptr, nullptr, SW_SHOW); - } else { - MessageBoxW(nullptr, L"An error occured while updating.", - nullptr, MB_ICONERROR | MB_OK); - // Revert to backup (App.exe.bak -> App.exe) - MoveFileExW(backup, argv[1], dwFlags); - } - - return 0; -} \ No newline at end of file diff --git a/update.cpp b/update.cpp deleted file mode 100644 index 4007ad7e6..000000000 --- a/update.cpp +++ /dev/null @@ -1,184 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" -#include - -#include "update.h" - -#include "common.h" -#include "foreach.h" -#include "resource.h" -#include "settings.h" -#include "string.h" -#include "taiga.h" -#include "xml.h" - -#include "dlg/dlg_main.h" -#include "dlg/dlg_update.h" -#include "dlg/dlg_update_new.h" - -#include "win32/win_taskdialog.h" - -// ============================================================================= - -UpdateHelper::UpdateHelper() - : app_(nullptr), - restart_required_(false), - update_available_(false) { -} - -// ============================================================================= - -bool UpdateHelper::Check(win32::App& app) { - app_ = &app; - - wstring address = L"taiga.erengy.com/update.php?"; - std::map parameters; - parameters[L"channel"] = L"stable"; - parameters[L"username"] = Settings.Account.MAL.user; - parameters[L"version"] = APP_VERSION; - parameters[L"check"] = MainDialog.IsWindow() ? L"manual" : L"auto"; - foreach_c_(parameter, parameters) { - if (parameter != parameters.begin()) address += L"&"; - address += parameter->first + L"=" + EncodeUrl(parameter->second); - } - - return client.Get(win32::Url(address), L"", HTTP_UpdateCheck); -} - -bool UpdateHelper::ParseData(wstring data) { - // Reset values - items.clear(); - download_path_.clear(); - latest_guid_.clear(); - restart_required_ = false; - update_available_ = false; - - // Load XML data - xml_document doc; - xml_parse_result result = doc.load(data.c_str()); - if (result.status != status_ok) { - return false; - } - - // Read channel information - xml_node channel = doc.child(L"rss").child(L"channel"); - - // Read items - for (xml_node item = channel.child(L"item"); item; item = item.next_sibling(L"item")) { - items.resize(items.size() + 1); - items.back().guid = XML_ReadStrValue(item, L"guid"); - items.back().category = XML_ReadStrValue(item, L"category"); - items.back().link = XML_ReadStrValue(item, L"link"); - items.back().description = XML_ReadStrValue(item, L"description"); - items.back().pub_date = XML_ReadStrValue(item, L"pubDate"); - } - - // Get version information - auto current_version = GetVersionValue(app_->GetVersionMajor(), - app_->GetVersionMinor(), - app_->GetVersionRevision()); - auto latest_version = current_version; - foreach_(item, items) { - vector numbers; - Split(item->guid, L".", numbers); - auto item_version = GetVersionValue(ToInt(numbers.at(0)), - ToInt(numbers.at(1)), - ToInt(numbers.at(2))); - if (item_version > latest_version) { - latest_guid_ = item->guid; - latest_version = item_version; - } - } - - // Compare version information - if (latest_version > current_version) - update_available_ = true; - - return true; -} - -bool UpdateHelper::IsRestartRequired() const { - return restart_required_; -} - -bool UpdateHelper::IsUpdateAvailable() const { - return update_available_; -} - -bool UpdateHelper::IsDownloadAllowed() const { - if (IsUpdateAvailable()) { - NewUpdateDialog.Create(IDD_UPDATE_NEW, UpdateDialog.GetWindowHandle(), true); - return true; - - } else { - if (MainDialog.IsWindow()) { - win32::TaskDialog dlg(L"Update", TD_ICON_INFORMATION); - dlg.SetFooter(L"Current version: " APP_VERSION); - dlg.SetMainInstruction(L"No updates available. Taiga is up to date!"); - dlg.AddButton(L"OK", IDOK); - dlg.Show(UpdateDialog.GetWindowHandle()); - } - return false; - } -} - -bool UpdateHelper::Download() { - auto feed_item = FindItem(latest_guid_); - if (!feed_item) return false; - - // TODO: Use TEMP folder path - download_path_ = AddTrailingSlash(GetPathOnly(app_->GetModulePath())); - download_path_ += GetFileName(feed_item->link); - - win32::Url url(feed_item->link); - - return client.Get(url, download_path_, HTTP_UpdateDownload); -} - -bool UpdateHelper::RunInstaller() { - auto feed_item = FindItem(latest_guid_); - if (!feed_item) return false; - - // /S runs the installer silently, /D overrides the default installation - // directory. Do not rely on the current directory here, as it isn't - // guaranteed to be the same as the module path. - wstring parameters = L"/S /D=" + GetPathOnly(app_->GetModulePath()); - - restart_required_ = Execute(download_path_, parameters); - return restart_required_; -} - -void UpdateHelper::SetDownloadPath(const wstring& path) { - download_path_ = path; -} - -const GenericFeedItem* UpdateHelper::FindItem(const wstring& guid) const { - foreach_(item, items) - if (IsEqual(item->guid, latest_guid_)) - return &(*item); - - return nullptr; -} - -unsigned long long UpdateHelper::GetVersionValue(int major, int minor, int revision) const { - return (major * static_cast(pow(10.0, 12))) + - (minor * static_cast(pow(10.0, 8))) + - revision; -} \ No newline at end of file diff --git a/win32/win_control.h b/win32/win_control.h deleted file mode 100644 index 5b4bdd83c..000000000 --- a/win32/win_control.h +++ /dev/null @@ -1,418 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef WIN_CONTROL_H -#define WIN_CONTROL_H - -#include -#include -#include - -#include "win_main.h" -#include "win_window.h" - -namespace win32 { - -// ============================================================================= - -/* ComboBox */ - -class ComboBox : public Window { -public: - ComboBox() {} - ComboBox(HWND hWnd) { SetWindowHandle(hWnd); } - virtual ~ComboBox() {} - - int AddItem(LPCWSTR lpsz, LPARAM data); - int AddString(LPCWSTR lpsz); - int DeleteString(int index); - int FindItemData(LPARAM data); - int GetCount(); - LRESULT GetItemData(int index); - int GetCurSel(); - void ResetContent(); - BOOL SetCueBannerText(LPCWSTR lpcwText); - int SetCurSel(int index); - BOOL SetEditSel(int ichStart, int ichEnd); - int SetItemData(int index, LPARAM data); - -protected: - virtual void PreCreate(CREATESTRUCT &cs); - virtual void OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct); -}; - -// ============================================================================= - -/* Edit */ - -class Edit : public Window { -public: - Edit() {} - Edit(HWND hWnd) { SetWindowHandle(hWnd); } - virtual ~Edit() {} - - void GetRect(LPRECT lprc); - void LimitText(int cchMax); - BOOL SetCueBannerText(LPCWSTR lpcwText, BOOL fDrawFocused = FALSE); - void SetMargins(int iLeft = -1, int iRight = -1); - void SetMultiLine(BOOL bEnabled); - void SetPasswordChar(UINT ch); - void SetReadOnly(BOOL bReadOnly); - void SetRect(LPRECT lprc); - void SetSel(int ichStart, int ichEnd); - BOOL ShowBalloonTip(LPCWSTR pszText, LPCWSTR pszTitle, INT ttiIcon); - -protected: - virtual void PreCreate(CREATESTRUCT &cs); - virtual void OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct); -}; - -// ============================================================================= - -/* Image list */ - -class ImageList { -public: - ImageList() { m_hImageList = NULL; } - ~ImageList() { Destroy(); } - void operator = (const HIMAGELIST hImageList) { - SetHandle(hImageList); - } - - int AddBitmap(HBITMAP hBitmap, COLORREF crMask); - BOOL BeginDrag(int iTrack, int dxHotspot, int dyHotspot); - BOOL Create(int cx, int cy); - VOID Destroy(); - BOOL DragEnter(HWND hwndLock, int x, int y); - BOOL DragLeave(HWND hwndLock); - BOOL DragMove(int x, int y); - BOOL Draw(int nIndex, HDC hdcDest, int x, int y); - VOID EndDrag(); - HIMAGELIST GetHandle(); - HICON GetIcon(int nIndex); - BOOL Remove(int index); - VOID SetHandle(HIMAGELIST hImageList); - -private: - HIMAGELIST m_hImageList; -}; - -// ============================================================================= - -/* List view */ - -class ListView : public Window { -public: - ListView(); - ListView(HWND hWnd) { SetWindowHandle(hWnd); } - virtual ~ListView() {} - - HIMAGELIST CreateDragImage(int iItem, LPPOINT lpptUpLeft); - BOOL DeleteAllItems(); - BOOL DeleteItem(int iItem); - int EnableGroupView(bool bValue); - BOOL EnsureVisible(int i); - BOOL GetCheckState(UINT iIndex); - HWND GetHeader(); - int GetItemCount(); - LPARAM GetItemParam(int i); - void GetItemText(int iItem, int iSubItem, LPWSTR pszText, int cchTextMax = MAX_PATH); - void GetItemText(int iItem, int iSubItem, wstring& str, int cchTextMax = MAX_PATH); - INT GetNextItem(int iStart, UINT flags); - INT GetNextItemIndex(int iItem, int iGroup, LPARAM flags); - UINT GetSelectedCount(); - INT GetSelectionMark(); - int GetSortColumn(); - int GetSortOrder(); - int GetSortType(); - BOOL GetSubItemRect(int iItem, int iSubItem, LPRECT lpRect); - DWORD GetView(); - int HitTest(bool return_subitem = false); - int HitTestEx(LPLVHITTESTINFO lplvhi); - int InsertColumn(int nIndex, int nWidth, int nWidthMin, int nAlign, LPCWSTR szText); - int InsertGroup(int nIndex, LPCWSTR szText, bool bCollapsable = false, bool bCollapsed = false); - int InsertItem(const LVITEM& lvi); - int InsertItem(int nIndex, int nGroup, int nIcon, UINT cColumns, PUINT puColumns, LPCWSTR pszText, LPARAM lParam); - BOOL IsGroupViewEnabled(); - BOOL RedrawItems(int iFirst, int iLast, bool repaint); - void RemoveAllGroups(); - BOOL SetBkImage(HBITMAP hbmp, ULONG ulFlags = LVBKIF_TYPE_WATERMARK, int xOffset = 100, int yOffset = 100); - void SetCheckState(int iIndex, BOOL fCheck); - BOOL SetColumnWidth(int iCol, int cx); - void SetExtendedStyle(DWORD dwExStyle); - int SetGroupText(int nIndex, LPCWSTR szText); - DWORD SetHoverTime(DWORD dwHoverTime); - void SetImageList(HIMAGELIST hImageList, int iImageList = LVSIL_SMALL); - BOOL SetItem(int nIndex, int nSubItem, LPCWSTR szText); - BOOL SetItemIcon(int nIndex, int nIcon); - void SetSelectedItem(int iIndex); - BOOL SetTileViewInfo(PLVTILEVIEWINFO plvtvinfo); - BOOL SetTileViewInfo(int cLines, DWORD dwFlags, RECT* rcLabelMargin = NULL, SIZE* sizeTile = NULL); - int SetView(DWORD iView); - void Sort(int iColumn, int iOrder, int iType, PFNLVCOMPARE pfnCompare); - -protected: - virtual void PreCreate(CREATESTRUCT &cs); - virtual void OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct); - -private: - int m_iSortColumn, m_iSortOrder, m_iSortType; -}; - -// ============================================================================= - -/* Progress bar */ - -class ProgressBar : public Window { -public: - ProgressBar() {} - ProgressBar(HWND hWnd) { SetWindowHandle(hWnd); } - virtual ~ProgressBar() {} - - UINT GetPosition(); - void SetMarquee(bool enabled); - UINT SetPosition(UINT position); - DWORD SetRange(UINT min, UINT max); - UINT SetState(UINT state); - -protected: - virtual void PreCreate(CREATESTRUCT &cs); - virtual void OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct); -}; - -// ============================================================================= - -/* Rebar */ - -class Rebar : public Window { -public: - Rebar() {} - Rebar(HWND hWnd) { SetWindowHandle(hWnd); } - virtual ~Rebar() {} - - UINT GetBarHeight(); - BOOL InsertBand(LPREBARBANDINFO lpBarInfo); - BOOL InsertBand(HWND hwndChild, UINT cx, UINT cxHeader, UINT cxIdeal, UINT cxMinChild, - UINT cyChild, UINT cyIntegral, UINT cyMaxChild, UINT cyMinChild, - UINT fMask, UINT fStyle); - -protected: - virtual void PreCreate(CREATESTRUCT &cs); - virtual void OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct); -}; - -// ============================================================================= - -/* Rich edit */ - -class RichEdit : public Window { -public: - RichEdit(); - RichEdit(HWND hWnd) { SetWindowHandle(hWnd); } - virtual ~RichEdit(); - - void GetSel(CHARRANGE* cr); - wstring GetTextRange(CHARRANGE* cr); - void HideSelection(BOOL bHide); - BOOL SetCharFormat(DWORD dwFormat, CHARFORMAT* cf); - DWORD SetEventMask(DWORD dwFlags); - void SetSel(int ichStart, int ichEnd); - void SetSel(CHARRANGE* cr); - UINT SetTextEx(const string& str); - -protected: - virtual void PreCreate(CREATESTRUCT &cs); - virtual void OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct); - -private: - HMODULE m_hInstRichEdit; -}; - -// ============================================================================= - -/* Spin */ - -class Spin : public Window { -public: - Spin() {} - Spin(HWND hWnd) { SetWindowHandle(hWnd); } - virtual ~Spin() {} - - bool GetPos32(int& value); - HWND SetBuddy(HWND hwnd); - int SetPos32(int position); - void SetRange32(int lower_limit, int upper_limit); -}; - -// ============================================================================= - -/* Status bar */ - -class StatusBar : public Window { -public: - StatusBar() {} - StatusBar(HWND hWnd) { SetWindowHandle(hWnd); } - virtual ~StatusBar() {} - - int InsertPart(int iImage, int iStyle, int iAutosize, int iWidth, LPCWSTR lpText, LPCWSTR lpTooltip); - void SetImageList(HIMAGELIST hImageList); - void SetPartText(int iPart, LPCWSTR lpText); - void SetPartTipText(int iPart, LPCWSTR lpTipText); - void SetPartWidth(int iPart, int iWidth); - -protected: - virtual void PreCreate(CREATESTRUCT &cs); - virtual void OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct); - -private: - HIMAGELIST m_hImageList; - vector m_iWidth; -}; - -// ============================================================================= - -/* SysLink */ - -class SysLink : public Window { -public: - SysLink() {} - SysLink(HWND hWnd) { SetWindowHandle(hWnd); } - virtual ~SysLink() {} - - void SetItemState(int item, UINT states); -}; - -// ============================================================================= - -/* Tab */ - -class Tab : public Window { -public: - Tab() {} - Tab(HWND hWnd) { SetWindowHandle(hWnd); } - virtual ~Tab() {} - - void AdjustRect(HWND hWindow, BOOL fLarger, LPRECT lpRect); - int DeleteAllItems(); - int DeleteItem(int nIndex); - int InsertItem(int nIndex, LPCWSTR szText, LPARAM lParam); - int GetCurrentlySelected(); - int GetItemCount(); - LPARAM GetItemParam(int nIndex); - int HitTest(); - int SetCurrentlySelected(int iItem); - int SetItemText(int iItem, LPCWSTR szText); - -protected: - virtual void PreCreate(CREATESTRUCT &cs); - virtual void OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct); -}; - -// ============================================================================= - -/* Toolbar */ - -class Toolbar : public Window { -public: - Toolbar() {} - Toolbar(HWND hWnd) { SetWindowHandle(hWnd); } - virtual ~Toolbar() {} - - BOOL EnableButton(int idCommand, bool bEnabled); - int GetHeight(); - BOOL GetButton(int nIndex, TBBUTTON& tbb); - int GetButtonCount(); - DWORD GetButtonSize(); - DWORD GetButtonStyle(int nIndex); - LPCWSTR GetButtonTooltip(int nIndex); - DWORD GetPadding(); - int HitTest(POINT& pt); - BOOL InsertButton(int iIndex, int iBitmap, int idCommand, - BYTE fsState, BYTE fsStyle, DWORD_PTR dwData, - LPCWSTR lpText, LPCWSTR lpTooltip); - BOOL PressButton(int idCommand, BOOL bPress); - BOOL SetButtonImage(int nIndex, int iImage); - BOOL SetButtonText(int nIndex, LPCWSTR lpText); - BOOL SetButtonTooltip(int nIndex, LPCWSTR lpTooltip); - void SetImageList(HIMAGELIST hImageList, int dxBitmap, int dyBitmap); - -protected: - virtual void PreCreate(CREATESTRUCT &cs); - virtual void OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct); - -private: - vector m_TooltipText; -}; - -// ============================================================================= - -/* Tooltip */ - -class Tooltip : public Window { -public: - Tooltip() {} - Tooltip(HWND hWnd) { SetWindowHandle(hWnd); } - virtual ~Tooltip() {} - - BOOL AddTip(UINT uID, LPCWSTR lpText, LPCWSTR lpTitle, LPRECT rcArea, bool bWindowID); - BOOL DeleteTip(UINT uID); - void SetDelayTime(long lAutopop, long lInitial, long lReshow); - void SetMaxWidth(long lWidth); - void UpdateText(UINT uID, LPCWSTR lpText); - void UpdateTitle(LPCWSTR lpTitle); - -protected: - virtual void PreCreate(CREATESTRUCT &cs); - virtual void OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct); -}; - -// ============================================================================= - -/* Tree view */ - -class TreeView : public Window { -public: - TreeView() {} - TreeView(HWND hWnd) { SetWindowHandle(hWnd); } - virtual ~TreeView() {} - - BOOL DeleteAllItems(); - BOOL DeleteItem(HTREEITEM hitem); - BOOL Expand(HTREEITEM hItem, bool bExpand = true); - UINT GetCheckState(HTREEITEM hItem); - UINT GetCount(); - BOOL GetItem(LPTVITEM pItem); - LPARAM GetItemData(HTREEITEM hItem); - HTREEITEM GetSelection(); - HTREEITEM HitTest(LPTVHITTESTINFO lpht, bool bGetCursorPos = false); - HTREEITEM InsertItem(LPCWSTR pszText, int iImage, LPARAM lParam, - HTREEITEM htiParent, HTREEITEM hInsertAfter = TVI_LAST); - BOOL SelectItem(HTREEITEM hItem); - UINT SetCheckState(HTREEITEM hItem, BOOL fCheck); - HIMAGELIST SetImageList(HIMAGELIST himl, INT iImage = TVSIL_NORMAL); - BOOL SetItem(HTREEITEM hItem, LPCWSTR pszText); - int SetItemHeight(SHORT cyItem); - -protected: - virtual void PreCreate(CREATESTRUCT &cs); - virtual void OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct); -}; - -} // namespace win32 - -#endif // WIN_CONTROL_H \ No newline at end of file diff --git a/win32/win_ctl_combobox.cpp b/win32/win_ctl_combobox.cpp deleted file mode 100644 index 812579885..000000000 --- a/win32/win_ctl_combobox.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_control.h" -#include - -namespace win32 { - -// ============================================================================= - -void ComboBox::PreCreate(CREATESTRUCT &cs) { - cs.dwExStyle = WS_EX_CLIENTEDGE; - cs.lpszClass = WC_COMBOBOX; - cs.style = CBS_DROPDOWN | CBS_HASSTRINGS | WS_CHILD | WS_TABSTOP | WS_VISIBLE; -} - -void ComboBox::OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { - Window::OnCreate(hwnd, lpCreateStruct); -} - -// ============================================================================= - -int ComboBox::AddItem(LPCWSTR lpsz, LPARAM data) { - int index = ComboBox_AddString(m_hWindow, lpsz); - return ComboBox_SetItemData(m_hWindow, index, data); -} - -int ComboBox::AddString(LPCWSTR lpsz) { - return ComboBox_AddString(m_hWindow, lpsz); -} - -int ComboBox::DeleteString(int index) { - return ComboBox_DeleteString(m_hWindow, index); -} - -int ComboBox::FindItemData(LPARAM data) { - for (int i = 0; i < GetCount(); i++) - if (data == GetItemData(i)) - return i; - - return CB_ERR; -} - -int ComboBox::GetCount() { - return ComboBox_GetCount(m_hWindow); -} - -int ComboBox::GetCurSel() { - return ComboBox_GetCurSel(m_hWindow); -} - -LRESULT ComboBox::GetItemData(int index) { - return ComboBox_GetItemData(m_hWindow, index); -} - -void ComboBox::ResetContent() { - ComboBox_ResetContent(m_hWindow); -} - -BOOL ComboBox::SetCueBannerText(LPCWSTR lpcwText) { - return ComboBox_SetCueBannerText(m_hWindow, lpcwText); -} - -int ComboBox::SetCurSel(int index) { - return ComboBox_SetCurSel(m_hWindow, index); -} - -BOOL ComboBox::SetEditSel(int ichStart, int ichEnd) { - return ::SendMessage(m_hWindow, CB_SETEDITSEL, ichStart, ichEnd); -} - -int ComboBox::SetItemData(int index, LPARAM data) { - return ComboBox_SetItemData(m_hWindow, index, data); -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_ctl_edit.cpp b/win32/win_ctl_edit.cpp deleted file mode 100644 index 13267550a..000000000 --- a/win32/win_ctl_edit.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_control.h" - -namespace win32 { - -// ============================================================================= - -void Edit::PreCreate(CREATESTRUCT &cs) { - cs.dwExStyle = WS_EX_CLIENTEDGE; - cs.lpszClass = L"EDIT"; - cs.style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | ES_LEFT | ES_AUTOHSCROLL; -} - -void Edit::OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { - Window::OnCreate(hwnd, lpCreateStruct); -} - -// ============================================================================= - -void Edit::GetRect(LPRECT lprc) { - ::SendMessage(m_hWindow, EM_GETRECT, 0, reinterpret_cast(lprc)); -} - -void Edit::LimitText(int cchMax) { - ::SendMessage(m_hWindow, EM_SETLIMITTEXT, cchMax, 0); -} - -BOOL Edit::SetCueBannerText(LPCWSTR lpcwText, BOOL fDrawFocused) { - return ::SendMessage(m_hWindow, EM_SETCUEBANNER, fDrawFocused, reinterpret_cast(lpcwText)); -} - -void Edit::SetMargins(int iLeft, int iRight) { - DWORD flags = 0; - if (iLeft > -1) flags |= EC_LEFTMARGIN; - if (iRight > -1) flags |= EC_RIGHTMARGIN; - ::SendMessage(m_hWindow, EM_SETMARGINS, flags, MAKELPARAM(iLeft, iRight)); -} - -void Edit::SetMultiLine(BOOL bEnabled) { - // TODO: We have to re-create the control, this does not work. - if (bEnabled) { - SetStyle(ES_MULTILINE | ES_AUTOVSCROLL, ES_AUTOHSCROLL); - } else { - SetStyle(ES_AUTOHSCROLL, ES_MULTILINE | ES_AUTOVSCROLL); - } -} - -void Edit::SetPasswordChar(UINT ch) { - ::SendMessage(m_hWindow, EM_SETPASSWORDCHAR, ch, 0); -} - -void Edit::SetReadOnly(BOOL bReadOnly) { - ::SendMessage(m_hWindow, EM_SETREADONLY, bReadOnly, 0); -} - -void Edit::SetRect(LPRECT lprc) { - ::SendMessage(m_hWindow, EM_SETRECT, 0, reinterpret_cast(lprc)); -} - -void Edit::SetSel(int ichStart, int ichEnd) { - ::SendMessage(m_hWindow, EM_SETSEL, ichStart, ichEnd); - ::SendMessage(m_hWindow, EM_SCROLLCARET, 0, 0); -} - -BOOL Edit::ShowBalloonTip(LPCWSTR pszText, LPCWSTR pszTitle, INT ttiIcon) { - EDITBALLOONTIP ebt; - ebt.cbStruct = sizeof(EDITBALLOONTIP); - ebt.pszText = pszText; - ebt.pszTitle = pszTitle; - ebt.ttiIcon = ttiIcon; - return ::SendMessage(m_hWindow, EM_SHOWBALLOONTIP, 0, reinterpret_cast(&ebt)); -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_ctl_imagelist.cpp b/win32/win_ctl_imagelist.cpp deleted file mode 100644 index 26940f47a..000000000 --- a/win32/win_ctl_imagelist.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_control.h" - -namespace win32 { - -// ============================================================================= - -BOOL ImageList::Create(int cx, int cy) { - Destroy(); - m_hImageList = ::ImageList_Create(cx, cy, ILC_COLOR32 | ILC_MASK, 0, 0); - return m_hImageList != NULL; -} - -VOID ImageList::Destroy() { - if (m_hImageList) { - ::ImageList_Destroy(m_hImageList); - m_hImageList = NULL; - } -} - -// ============================================================================= - -int ImageList::AddBitmap(HBITMAP hBitmap, COLORREF crMask) { - if (crMask != CLR_NONE) { - return ::ImageList_AddMasked(m_hImageList, hBitmap, crMask); - } else { - return ::ImageList_Add(m_hImageList, hBitmap, NULL); - } -} - -BOOL ImageList::BeginDrag(int iTrack, int dxHotspot, int dyHotspot) { - return ::ImageList_BeginDrag(m_hImageList, iTrack, dxHotspot, dyHotspot); -} - -BOOL ImageList::DragEnter(HWND hwndLock, int x, int y) { - return ::ImageList_DragEnter(hwndLock, x, y); -} - -BOOL ImageList::DragLeave(HWND hwndLock) { - return ::ImageList_DragLeave(hwndLock); -} - -BOOL ImageList::DragMove(int x, int y) { - return ::ImageList_DragMove(x, y); -} - -BOOL ImageList::Draw(int nIndex, HDC hdcDest, int x, int y) { - return ::ImageList_Draw(m_hImageList, nIndex, hdcDest, x, y, ILD_NORMAL); -} - -VOID ImageList::EndDrag() { - ImageList_EndDrag(); -} - -HIMAGELIST ImageList::GetHandle() { - return m_hImageList; -} - -HICON ImageList::GetIcon(int nIndex) { - return ::ImageList_GetIcon(m_hImageList, nIndex, ILD_NORMAL); -} - -BOOL ImageList::Remove(int index) { - return ::ImageList_Remove(m_hImageList, index); -} - -VOID ImageList::SetHandle(HIMAGELIST hImageList) { - Destroy(); - m_hImageList = hImageList; -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_ctl_listview.cpp b/win32/win_ctl_listview.cpp deleted file mode 100644 index 1542556ed..000000000 --- a/win32/win_ctl_listview.cpp +++ /dev/null @@ -1,339 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_control.h" - -namespace win32 { - -// ============================================================================= - -ListView::ListView() { - m_iSortColumn = -1; - m_iSortOrder = 1; - m_iSortType = 0; -} - -// ============================================================================= - -void ListView::PreCreate(CREATESTRUCT &cs) { - cs.dwExStyle = WS_EX_CLIENTEDGE; - cs.lpszClass = WC_LISTVIEW; - cs.style = WS_CHILD | WS_TABSTOP | WS_VISIBLE | LVS_ALIGNLEFT | LVS_AUTOARRANGE | - LVS_REPORT | LVS_SHAREIMAGELISTS | LVS_SINGLESEL; -} - -void ListView::OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { - ListView_SetExtendedListViewStyle(hwnd, LVS_EX_AUTOSIZECOLUMNS | LVS_EX_DOUBLEBUFFER | - LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP); - Window::OnCreate(hwnd, lpCreateStruct); -} - -// ============================================================================= - -int ListView::InsertColumn(int nIndex, int nWidth, int nWidthMin, int nAlign, LPCWSTR szText) { - LVCOLUMN lvc = {0}; - lvc.cx = nWidth; - lvc.cxMin = nWidthMin; - lvc.fmt = nAlign; - lvc.mask = LVCF_FMT | LVCF_TEXT | LVCF_WIDTH | (nWidthMin ? LVCF_MINWIDTH : NULL); - lvc.pszText = const_cast(szText); - - if (GetWinVersion() < VERSION_VISTA) { - lvc.cx = lvc.cxMin; - } - - return ListView_InsertColumn(m_hWindow, nIndex, &lvc); -} - -// ============================================================================= - -int ListView::EnableGroupView(bool bValue) { - return ListView_EnableGroupView(m_hWindow, bValue); -} - -int ListView::InsertGroup(int nIndex, LPCWSTR szText, bool bCollapsable, bool bCollapsed) { - LVGROUP lvg = {0}; - lvg.cbSize = sizeof(LVGROUP); - lvg.iGroupId = nIndex; - lvg.mask = LVGF_HEADER | LVGF_GROUPID; - lvg.pszHeader = const_cast(szText); - - if (bCollapsable && GetWinVersion() >= VERSION_VISTA) { - lvg.mask |= LVGF_STATE; - lvg.state = LVGS_COLLAPSIBLE; - if (bCollapsed) lvg.state |= LVGS_COLLAPSED; - } - - return ListView_InsertGroup(m_hWindow, nIndex, &lvg); -} - -BOOL ListView::IsGroupViewEnabled() { - return ListView_IsGroupViewEnabled(m_hWindow); -} - -int ListView::SetGroupText(int nIndex, LPCWSTR szText) { - LVGROUP lvg = {0}; - lvg.cbSize = sizeof(LVGROUP); - lvg.mask = LVGF_HEADER; - lvg.pszHeader = const_cast(szText); - return ListView_SetGroupInfo(m_hWindow, nIndex, &lvg); -} - -// ============================================================================= - -HIMAGELIST ListView::CreateDragImage(int iItem, LPPOINT lpptUpLeft) { - return ListView_CreateDragImage(m_hWindow, iItem, lpptUpLeft); -} - -BOOL ListView::DeleteAllItems() { - return ListView_DeleteAllItems(m_hWindow); -} - -BOOL ListView::DeleteItem(int iItem) { - return ListView_DeleteItem(m_hWindow, iItem); -} - -BOOL ListView::EnsureVisible(int i) { - return ListView_EnsureVisible(m_hWindow, i, false); -} - -BOOL ListView::GetCheckState(UINT iIndex) { - return ListView_GetCheckState(m_hWindow, iIndex); -} - -HWND ListView::GetHeader() { - return ListView_GetHeader(m_hWindow); -} - -int ListView::GetItemCount() { - return ListView_GetItemCount(m_hWindow); -} - -LPARAM ListView::GetItemParam(int i) { - LVITEM lvi = {0}; - lvi.iItem = i; - lvi.mask = LVIF_PARAM; - - if (ListView_GetItem(m_hWindow, &lvi)) { - return lvi.lParam; - } else { - return NULL; - } -} - -void ListView::GetItemText(int iItem, int iSubItem, LPWSTR pszText, int cchTextMax) { - ListView_GetItemText(m_hWindow, iItem, iSubItem, pszText, cchTextMax); -} - -void ListView::GetItemText(int iItem, int iSubItem, wstring& str, int cchTextMax) { - vector buffer(cchTextMax); - ListView_GetItemText(m_hWindow, iItem, iSubItem, &buffer[0], cchTextMax); - str.assign(&buffer[0]); -} - -INT ListView::GetNextItem(int iStart, UINT flags) { - return ListView_GetNextItem(m_hWindow, iStart, flags); -} - -INT ListView::GetNextItemIndex(int iItem, int iGroup, LPARAM flags) { - LVITEMINDEX lvii; - lvii.iItem = iItem; - lvii.iGroup = iGroup; - if (ListView_GetNextItemIndex(m_hWindow, &lvii, flags)) { - return lvii.iItem; - } else { - return -1; - } -} - -UINT ListView::GetSelectedCount() { - return ListView_GetSelectedCount(m_hWindow); -} - -INT ListView::GetSelectionMark() { - return ListView_GetSelectionMark(m_hWindow); -} - -BOOL ListView::GetSubItemRect(int iItem, int iSubItem, LPRECT lpRect) { - return ListView_GetSubItemRect(m_hWindow, iItem, iSubItem, LVIR_BOUNDS, lpRect); -} - -DWORD ListView::GetView() { - return ListView_GetView(m_hWindow); -} - -int ListView::HitTest(bool return_subitem) { - LVHITTESTINFO lvhi; - ::GetCursorPos(&lvhi.pt); - ::ScreenToClient(m_hWindow, &lvhi.pt); - ListView_SubItemHitTestEx(m_hWindow, &lvhi); - return return_subitem ? lvhi.iSubItem : lvhi.iItem; -} - -int ListView::HitTestEx(LPLVHITTESTINFO lplvhi) { - ::GetCursorPos(&lplvhi->pt); - ::ScreenToClient(m_hWindow, &lplvhi->pt); - ListView_SubItemHitTestEx(m_hWindow, lplvhi); - return lplvhi->iItem; -} - -int ListView::InsertItem(const LVITEM& lvi) { - return ListView_InsertItem(m_hWindow, &lvi); -} - -int ListView::InsertItem(int iItem, int iGroupId, int iImage, UINT cColumns, PUINT puColumns, LPCWSTR pszText, LPARAM lParam) { - LVITEM lvi = {0}; - lvi.cColumns = cColumns; - lvi.iGroupId = iGroupId; - lvi.iImage = iImage; - lvi.iItem = iItem; - lvi.lParam = lParam; - lvi.puColumns = puColumns; - lvi.pszText = const_cast(pszText); - - if (cColumns != 0) lvi.mask |= LVIF_COLUMNS; - if (iGroupId > -1) lvi.mask |= LVIF_GROUPID; - if (iImage > -1) lvi.mask |= LVIF_IMAGE; - if (lParam != 0) lvi.mask |= LVIF_PARAM; - if (pszText != NULL) lvi.mask |= LVIF_TEXT; - - return ListView_InsertItem(m_hWindow, &lvi); -} - -BOOL ListView::RedrawItems(int iFirst, int iLast, bool repaint) { - BOOL return_value = ListView_RedrawItems(m_hWindow, iFirst, iLast); - if (return_value && repaint) ::UpdateWindow(m_hWindow); - return return_value; -} - -void ListView::RemoveAllGroups() { - ListView_RemoveAllGroups(m_hWindow); -} - -BOOL ListView::SetBkImage(HBITMAP hbmp, ULONG ulFlags, int xOffset, int yOffset) { - LVBKIMAGE bki = {0}; - bki.hbm = hbmp; - bki.ulFlags = ulFlags; - bki.xOffsetPercent = xOffset; - bki.yOffsetPercent = yOffset; - return ListView_SetBkImage(m_hWindow, &bki); -} - -void ListView::SetCheckState(int iIndex, BOOL fCheck) { - ListView_SetItemState(m_hWindow, iIndex, INDEXTOSTATEIMAGEMASK((fCheck==TRUE) ? 2 : 1), LVIS_STATEIMAGEMASK); -} - -BOOL ListView::SetColumnWidth(int iCol, int cx) { - // LVSCW_AUTOSIZE or LVSCW_AUTOSIZE_USEHEADER can be used as cx - return ListView_SetColumnWidth(m_hWindow, iCol, cx); -} - -void ListView::SetExtendedStyle(DWORD dwExStyle) { - ListView_SetExtendedListViewStyle(m_hWindow, dwExStyle); -} - -DWORD ListView::SetHoverTime(DWORD dwHoverTime) { - return ::SendMessage(m_hWindow, LVM_SETHOVERTIME, 0, dwHoverTime); -} - -void ListView::SetImageList(HIMAGELIST hImageList, int iImageList) { - ListView_SetImageList(m_hWindow, hImageList, iImageList); -} - -BOOL ListView::SetItem(int nIndex, int nSubItem, LPCWSTR szText) { - LVITEM lvi = {0}; - lvi.iItem = nIndex; - lvi.iSubItem = nSubItem; - lvi.mask = LVIF_TEXT; - lvi.pszText = const_cast(szText); - - return ListView_SetItem(m_hWindow, &lvi); -} - -BOOL ListView::SetItemIcon(int nIndex, int nIcon) { - LVITEM lvi = {0}; - lvi.iImage = nIcon; - lvi.iItem = nIndex; - lvi.mask = LVIF_IMAGE; - - return ListView_SetItem(m_hWindow, &lvi); -} - -void ListView::SetSelectedItem(int iIndex) { - ListView_SetItemState(m_hWindow, iIndex, LVIS_SELECTED, LVIS_SELECTED); -} - -BOOL ListView::SetTileViewInfo(PLVTILEVIEWINFO plvtvinfo) { - return ListView_SetTileViewInfo(m_hWindow, plvtvinfo); -} - -BOOL ListView::SetTileViewInfo(int cLines, DWORD dwFlags, RECT* rcLabelMargin, SIZE* sizeTile) { - LVTILEVIEWINFO tvi = {0}; - tvi.cbSize = sizeof(LVTILEVIEWINFO); - - tvi.dwFlags = dwFlags; - - if (cLines) { - tvi.dwMask |= LVTVIM_COLUMNS; - tvi.cLines = cLines; - } - if (sizeTile) { - tvi.dwMask |= LVTVIM_TILESIZE; - tvi.sizeTile = *sizeTile; - } - if (rcLabelMargin) { - tvi.dwMask |= LVTVIM_LABELMARGIN; - tvi.rcLabelMargin = *rcLabelMargin; - } - - return ListView_SetTileViewInfo(m_hWindow, &tvi); -} - -int ListView::SetView(DWORD iView) { - return ListView_SetView(m_hWindow, iView); -} - -// ============================================================================= - -int ListView::GetSortColumn() { return m_iSortColumn; } -int ListView::GetSortOrder() { return m_iSortOrder; } -int ListView::GetSortType() { return m_iSortType; } - -void ListView::Sort(int iColumn, int iOrder, int iType, PFNLVCOMPARE pfnCompare) { - m_iSortColumn = iColumn; - m_iSortOrder = (iOrder == 0)? 1 : iOrder; - m_iSortType = iType; - - ListView_SortItemsEx(m_hWindow, pfnCompare, this); - - if (GetWinVersion() < VERSION_VISTA) { - HDITEM hdi = {0}; - hdi.mask = HDI_FORMAT; - HWND hHeader = ListView_GetHeader(m_hWindow); - for (int i = 0; i < Header_GetItemCount(hHeader); ++i) { - Header_GetItem(hHeader, i, &hdi); - hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP); - Header_SetItem(hHeader, i, &hdi); - } - Header_GetItem(hHeader, iColumn, &hdi); - hdi.fmt |= (iOrder > -1 ? HDF_SORTUP : HDF_SORTDOWN); - Header_SetItem(hHeader, iColumn, &hdi); - } -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_ctl_rebar.cpp b/win32/win_ctl_rebar.cpp deleted file mode 100644 index 9245272a6..000000000 --- a/win32/win_ctl_rebar.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_control.h" - -namespace win32 { - -// ============================================================================= - -void Rebar::PreCreate(CREATESTRUCT &cs) { - cs.dwExStyle = 0; - cs.lpszClass = REBARCLASSNAME; - cs.style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | CCS_NODIVIDER | RBS_BANDBORDERS | RBS_VARHEIGHT; -} - -void Rebar::OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { - Window::OnCreate(hwnd, lpCreateStruct); -} - -// ============================================================================= - -UINT Rebar::GetBarHeight() { - return ::SendMessage(m_hWindow, RB_GETBARHEIGHT, 0, 0); -} - -BOOL Rebar::InsertBand(LPREBARBANDINFO lpBarInfo) { - return ::SendMessage(m_hWindow, RB_INSERTBAND, -1, reinterpret_cast(lpBarInfo)); -} - -BOOL Rebar::InsertBand(HWND hwndChild, UINT cx, UINT cxHeader, UINT cxIdeal, UINT cxMinChild, - UINT cyChild, UINT cyIntegral, UINT cyMaxChild, UINT cyMinChild, - UINT fMask, UINT fStyle) { - REBARBANDINFO rbi = {0}; - rbi.cbSize = REBARBANDINFO_V6_SIZE; - rbi.cx = cx; - rbi.cxHeader = cxHeader; - rbi.cxIdeal = cxIdeal; - rbi.cxMinChild = cxMinChild; - rbi.cyChild = cyChild; - rbi.cyIntegral = cyIntegral; - rbi.cyMaxChild = cyMaxChild; - rbi.cyMinChild = cyMinChild; - rbi.fMask = fMask; - rbi.fStyle = fStyle; - rbi.hwndChild = hwndChild; - - return ::SendMessage(m_hWindow, RB_INSERTBAND, -1, reinterpret_cast(&rbi)); -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_ctl_richedit.cpp b/win32/win_ctl_richedit.cpp deleted file mode 100644 index eee01c638..000000000 --- a/win32/win_ctl_richedit.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_control.h" - -namespace win32 { - -// ============================================================================= - -RichEdit::RichEdit() { - m_hInstRichEdit = ::LoadLibrary(L"riched20.dll"); -} - -RichEdit::~RichEdit() { - if (m_hInstRichEdit) { - ::FreeLibrary(m_hInstRichEdit); - } -} - -void RichEdit::PreCreate(CREATESTRUCT &cs) { - cs.dwExStyle = WS_EX_STATICEDGE; - cs.lpszClass = RICHEDIT_CLASS; - cs.style = WS_CHILD | WS_CLIPCHILDREN | WS_TABSTOP | WS_VISIBLE | WS_VSCROLL | ES_AUTOHSCROLL; -} - -void RichEdit::OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { - Window::OnCreate(hwnd, lpCreateStruct); -} - -// ============================================================================= - -void RichEdit::GetSel(CHARRANGE* cr) { - ::SendMessage(m_hWindow, EM_EXGETSEL, 0, reinterpret_cast(cr)); -} - -wstring RichEdit::GetTextRange(CHARRANGE* cr) { - wstring str(1024, '\0'); - - TEXTRANGE tr; - tr.chrg.cpMax = cr->cpMax; - tr.chrg.cpMin = cr->cpMin; - tr.lpstrText = (LPWSTR)str.data(); - - ::SendMessage(m_hWindow, EM_GETTEXTRANGE , 0, reinterpret_cast(&tr)); - - return str; -} - -void RichEdit::HideSelection(BOOL bHide) { - ::SendMessage(m_hWindow, EM_HIDESELECTION, bHide, 0); -} - -BOOL RichEdit::SetCharFormat(DWORD dwFormat, CHARFORMAT* cf) { - return ::SendMessage(m_hWindow, EM_SETCHARFORMAT, dwFormat, reinterpret_cast(cf)); -} - -DWORD RichEdit::SetEventMask(DWORD dwFlags) { - return ::SendMessage(m_hWindow, EM_SETEVENTMASK, 0, dwFlags); -} - -void RichEdit::SetSel(int ichStart, int ichEnd) { - ::SendMessage(m_hWindow, EM_SETSEL, ichStart, ichEnd); -} - -void RichEdit::SetSel(CHARRANGE* cr) { - ::SendMessage(m_hWindow, EM_EXSETSEL, 0, reinterpret_cast(cr)); -} - -UINT RichEdit::SetTextEx(const string& str) { - SETTEXTEX ste; - return ::SendMessage(m_hWindow, EM_SETTEXTEX, - reinterpret_cast(&ste), - reinterpret_cast(str.data())); -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_ctl_statusbar.cpp b/win32/win_ctl_statusbar.cpp deleted file mode 100644 index e012fdb9e..000000000 --- a/win32/win_ctl_statusbar.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_control.h" - -namespace win32 { - -// ============================================================================= - -void StatusBar::PreCreate(CREATESTRUCT &cs) { - cs.dwExStyle = NULL; - cs.lpszClass = STATUSCLASSNAME; - cs.style = WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP | SBARS_TOOLTIPS; -} - -void StatusBar::OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { - m_iWidth.clear(); - Window::OnCreate(hwnd, lpCreateStruct); -} - -// ============================================================================= - -int StatusBar::InsertPart(int iImage, int iStyle, int iAutosize, int iWidth, LPCWSTR lpText, LPCWSTR lpTooltip) { - if (m_iWidth.empty()) { - m_iWidth.push_back(iWidth); - } else { - m_iWidth.push_back(m_iWidth.back() + iWidth); - } - - int nParts = ::SendMessage(m_hWindow, SB_GETPARTS, 0, 0); - - ::SendMessage(m_hWindow, SB_SETPARTS, nParts + 1, reinterpret_cast(&m_iWidth[0])); - ::SendMessage(m_hWindow, SB_SETTEXT, nParts - 1, reinterpret_cast(lpText)); - ::SendMessage(m_hWindow, SB_SETTIPTEXT, nParts - 1, reinterpret_cast(lpTooltip)); - - if (iImage > -1 && m_hImageList) { - HICON hIcon = ::ImageList_GetIcon(m_hImageList, iImage, 0); - ::SendMessage(m_hWindow, SB_SETICON, nParts - 1, reinterpret_cast(hIcon)); - } - - return nParts; -} - -void StatusBar::SetImageList(HIMAGELIST hImageList) { - m_hImageList = hImageList; -} - -void StatusBar::SetPartText(int iPart, LPCWSTR lpText) { - ::SendMessage(m_hWindow, SB_SETTEXT, iPart, reinterpret_cast(lpText)); -} - -void StatusBar::SetPartTipText(int iPart, LPCWSTR lpTipText) { - ::SendMessage(m_hWindow, SB_SETTIPTEXT, iPart, reinterpret_cast(lpTipText)); -} - -void StatusBar::SetPartWidth(int iPart, int iWidth) { - if (iPart > static_cast(m_iWidth.size()) - 1) return; - if (iPart == 0) { - m_iWidth.at(iPart) = iWidth; - } else { - m_iWidth.at(iPart) = m_iWidth.at(iPart - 1) + iWidth; - } - - ::SendMessage(m_hWindow, SB_SETPARTS, m_iWidth.size(), reinterpret_cast(&m_iWidth[0])); -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_ctl_tab.cpp b/win32/win_ctl_tab.cpp deleted file mode 100644 index d1105540e..000000000 --- a/win32/win_ctl_tab.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_control.h" - -namespace win32 { - -// ============================================================================= - -void Tab::PreCreate(CREATESTRUCT &cs) { - cs.dwExStyle = NULL; - cs.lpszClass = WC_TABCONTROL; - cs.style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | TCS_TOOLTIPS; -} - -void Tab::OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { - TabCtrl_SetExtendedStyle(hwnd, TCS_EX_REGISTERDROP); - Window::OnCreate(hwnd, lpCreateStruct); -} - -// ============================================================================= - -void Tab::AdjustRect(HWND hWindow, BOOL fLarger, LPRECT lpRect) { - if (hWindow) { - RECT window_rect; - ::GetClientRect(m_hWindow, lpRect); - ::GetWindowRect(m_hWindow, &window_rect); - ::ScreenToClient(hWindow, (LPPOINT)&window_rect); - ::SetRect(lpRect, - window_rect.left, window_rect.top, - window_rect.left + lpRect->right, - window_rect.top + lpRect->bottom); - } - TabCtrl_AdjustRect(m_hWindow, fLarger, lpRect); -} - -int Tab::DeleteAllItems() { - return TabCtrl_DeleteAllItems(m_hWindow); -} - -int Tab::DeleteItem(int nIndex) { - return TabCtrl_DeleteItem(m_hWindow, nIndex); -} - -int Tab::InsertItem(int nIndex, LPCWSTR szText, LPARAM lParam) { - TCITEM tci; - tci.mask = TCIF_PARAM | TCIF_TEXT; - tci.pszText = (LPWSTR)szText; - tci.lParam = lParam; - tci.iImage = -1; - - return TabCtrl_InsertItem(m_hWindow, nIndex, &tci); -} - -int Tab::GetCurrentlySelected() { - return TabCtrl_GetCurSel(m_hWindow); -} - -int Tab::GetItemCount() { - return TabCtrl_GetItemCount(m_hWindow); -} - -LPARAM Tab::GetItemParam(int nIndex) { - TCITEM tci; - tci.mask = TCIF_PARAM; - TabCtrl_GetItem(m_hWindow, nIndex, &tci); - return tci.lParam; -} - -int Tab::HitTest() { - TCHITTESTINFO tchti; - ::GetCursorPos(&tchti.pt); - ::ScreenToClient(m_hWindow, &tchti.pt); - return TabCtrl_HitTest(m_hWindow, &tchti); -} - -int Tab::SetCurrentlySelected(int iItem) { - return TabCtrl_SetCurSel(m_hWindow, iItem); -} - -int Tab::SetItemText(int iItem, LPCWSTR szText) { - TCITEM tci; - tci.mask = TCIF_TEXT; - tci.pszText = (LPWSTR)szText; - - return TabCtrl_SetItem(m_hWindow, iItem, &tci); -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_ctl_toolbar.cpp b/win32/win_ctl_toolbar.cpp deleted file mode 100644 index 2954d3fb8..000000000 --- a/win32/win_ctl_toolbar.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_control.h" - -namespace win32 { - -// ============================================================================= - -void Toolbar::PreCreate(CREATESTRUCT &cs) { - cs.dwExStyle = NULL; - cs.lpszClass = TOOLBARCLASSNAME; - cs.style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | CCS_NODIVIDER | CCS_NOPARENTALIGN | - TBSTYLE_FLAT | TBSTYLE_LIST | TBSTYLE_TOOLTIPS | TBSTYLE_TRANSPARENT; -} - -void Toolbar::OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { - ::SendMessage(m_hWindow, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0); - ::SendMessage(m_hWindow, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS | TBSTYLE_EX_MIXEDBUTTONS); - SetImageList(NULL, 0, 0); - Window::OnCreate(hwnd, lpCreateStruct); -} - -// ============================================================================= - -BOOL Toolbar::EnableButton(int idCommand, bool bEnabled) { - TBBUTTONINFO tbbi = {0}; - tbbi.cbSize = sizeof(TBBUTTONINFO); - tbbi.dwMask = TBIF_STATE; - - ::SendMessage(m_hWindow, TB_GETBUTTONINFO, idCommand, reinterpret_cast(&tbbi)); - - if (bEnabled) { - tbbi.fsState |= TBSTATE_ENABLED; - tbbi.fsState &= ~TBSTATE_INDETERMINATE; - } else { - tbbi.fsState |= TBSTATE_INDETERMINATE; - tbbi.fsState &= ~TBSTATE_ENABLED; - } - - return ::SendMessage(m_hWindow, TB_SETBUTTONINFO, idCommand, reinterpret_cast(&tbbi)); -} - -int Toolbar::GetHeight() { - RECT rect; - ::SendMessage(m_hWindow, TB_GETITEMRECT, 1, (LPARAM)&rect); - return rect.bottom; -} - -BOOL Toolbar::GetButton(int nIndex, TBBUTTON& tbb) { - return ::SendMessage(m_hWindow, TB_GETBUTTON, nIndex, reinterpret_cast(&tbb)); -} - -int Toolbar::GetButtonCount() { - return ::SendMessage(m_hWindow, TB_BUTTONCOUNT, 0, 0); -} - -DWORD Toolbar::GetButtonSize() { - return ::SendMessage(m_hWindow, TB_GETBUTTONSIZE, 0, 0); -} - -DWORD Toolbar::GetButtonStyle(int nIndex) { - TBBUTTON tbb = {0}; - ::SendMessage(m_hWindow, TB_GETBUTTON, nIndex, reinterpret_cast(&tbb)); - return tbb.fsStyle; -} - -LPCWSTR Toolbar::GetButtonTooltip(int nIndex) { - return nIndex < static_cast(m_TooltipText.size()) ? m_TooltipText[nIndex] : L""; -} - -DWORD Toolbar::GetPadding() { - return ::SendMessage(m_hWindow, TB_GETPADDING, 0, 0); -} - -int Toolbar::HitTest(POINT& pt) { - return ::SendMessage(m_hWindow, TB_HITTEST, 0, reinterpret_cast(&pt)); -} - -BOOL Toolbar::InsertButton(int iIndex, int iBitmap, int idCommand, BYTE fsState, BYTE fsStyle, - DWORD_PTR dwData, LPCWSTR lpText, LPCWSTR lpTooltip) { - TBBUTTON tbb = {0}; - tbb.iBitmap = iBitmap; - tbb.idCommand = idCommand; - tbb.iString = reinterpret_cast(lpText); - tbb.fsState = fsState; - tbb.fsStyle = fsStyle; - tbb.dwData = dwData; - - m_TooltipText.push_back(lpTooltip); - return ::SendMessage(m_hWindow, TB_INSERTBUTTON, iIndex, reinterpret_cast(&tbb)); -} - -BOOL Toolbar::PressButton(int idCommand, BOOL bPress) { - return ::SendMessage(m_hWindow, TB_PRESSBUTTON, idCommand, MAKELPARAM(bPress, 0)); -} - -BOOL Toolbar::SetButtonImage(int nIndex, int iImage) { - TBBUTTONINFO tbbi = {0}; - tbbi.cbSize = sizeof(TBBUTTONINFO); - tbbi.dwMask = TBIF_BYINDEX | TBIF_IMAGE; - tbbi.iImage = iImage; - return ::SendMessage(m_hWindow, TB_SETBUTTONINFO, nIndex, reinterpret_cast(&tbbi)); -} - -BOOL Toolbar::SetButtonText(int nIndex, LPCWSTR lpText) { - TBBUTTONINFO tbbi = {0}; - tbbi.cbSize = sizeof(TBBUTTONINFO); - tbbi.dwMask = TBIF_BYINDEX | TBIF_TEXT; - tbbi.pszText = const_cast(lpText); - return ::SendMessage(m_hWindow, TB_SETBUTTONINFO, nIndex, reinterpret_cast(&tbbi)); -} - -BOOL Toolbar::SetButtonTooltip(int nIndex, LPCWSTR lpTooltip) { - if (nIndex > static_cast(m_TooltipText.size())) { - return FALSE; - } else { - m_TooltipText[nIndex] = lpTooltip; - return TRUE; - } -} - -void Toolbar::SetImageList(HIMAGELIST hImageList, int dxBitmap, int dyBitmap) { - ::SendMessage(m_hWindow, TB_SETBITMAPSIZE, 0, (LPARAM)MAKELONG(dxBitmap, dyBitmap)); - ::SendMessage(m_hWindow, TB_SETIMAGELIST, 0, (LPARAM)hImageList); -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_ctl_tooltip.cpp b/win32/win_ctl_tooltip.cpp deleted file mode 100644 index 2755c6ff2..000000000 --- a/win32/win_ctl_tooltip.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_control.h" - -namespace win32 { - -// ============================================================================= - -void Tooltip::PreCreate(CREATESTRUCT &cs) { - cs.dwExStyle = NULL; - cs.lpszClass = TOOLTIPS_CLASS; - cs.style = WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP; -} - -void Tooltip::OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { - ::SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); - Window::OnCreate(hwnd, lpCreateStruct); -} - -// ============================================================================= - -BOOL Tooltip::AddTip(UINT uID, LPCWSTR lpText, LPCWSTR lpTitle, LPRECT rcArea, bool bWindowID) { - TOOLINFO ti; - ti.cbSize = sizeof(TOOLINFO); - ti.hwnd = m_hParent; - ti.hinst = m_hInstance; - ti.lpszText = (LPWSTR)lpText; - ti.uFlags = TTF_SUBCLASS | (bWindowID ? TTF_IDISHWND : NULL); - ti.uId = (UINT_PTR)uID; - if (rcArea) ti.rect = *rcArea; - - BOOL lResult = ::SendMessage(m_hWindow, TTM_ADDTOOL, 0, (LPARAM)&ti); - if (lResult && lpTitle) { - lResult = ::SendMessage(m_hWindow, TTM_SETTITLE, 1, (LPARAM)lpTitle); - } - return lResult; -} - -BOOL Tooltip::DeleteTip(UINT uID) { - TOOLINFO ti; - ti.cbSize = sizeof(TOOLINFO); - ti.hwnd = m_hParent; - ti.uId = (UINT_PTR)uID; - - return ::SendMessage(m_hWindow, TTM_DELTOOL, 0, (LPARAM)&ti); -} - -void Tooltip::SetDelayTime(long lAutopop, long lInitial, long lReshow) { - ::SendMessage(m_hWindow, TTM_SETDELAYTIME, TTDT_AUTOPOP, lAutopop); - ::SendMessage(m_hWindow, TTM_SETDELAYTIME, TTDT_INITIAL, lInitial); - ::SendMessage(m_hWindow, TTM_SETDELAYTIME, TTDT_RESHOW, lReshow); -} - -void Tooltip::SetMaxWidth(long lWidth) { - ::SendMessage(m_hWindow, TTM_SETMAXTIPWIDTH, NULL, lWidth); -} - -void Tooltip::UpdateText(UINT uID, LPCWSTR lpText) { - TOOLINFO ti; - ti.cbSize = sizeof(TOOLINFO); - ti.hinst = m_hInstance; - ti.hwnd = m_hParent; - ti.lpszText = (LPWSTR)lpText; - ti.uId = (UINT_PTR)uID; - - ::SendMessage(m_hWindow, TTM_UPDATETIPTEXT, NULL, (LPARAM)&ti); -} - -void Tooltip::UpdateTitle(LPCWSTR lpTitle) { - ::SendMessage(m_hWindow, TTM_SETTITLE, lpTitle ? 0 : 1, (LPARAM)lpTitle); -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_ctl_treeview.cpp b/win32/win_ctl_treeview.cpp deleted file mode 100644 index 9387455e1..000000000 --- a/win32/win_ctl_treeview.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_control.h" - -namespace win32 { - -// ============================================================================= - -void TreeView::PreCreate(CREATESTRUCT &cs) { - cs.dwExStyle = WS_EX_CLIENTEDGE; - cs.lpszClass = WC_TREEVIEW; - cs.style = WS_CHILD | WS_VISIBLE | WS_TABSTOP | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT; -} - -void TreeView::OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { - TreeView_SetExtendedStyle(hwnd, TVS_EX_DOUBLEBUFFER, NULL); - Window::OnCreate(hwnd, lpCreateStruct); -} - -// ============================================================================= - -BOOL TreeView::DeleteAllItems() { - return TreeView_DeleteAllItems(m_hWindow); -} - -BOOL TreeView::DeleteItem(HTREEITEM hitem) { - return TreeView_DeleteItem(m_hWindow, hitem); -} - -BOOL TreeView::Expand(HTREEITEM hItem, bool bExpand) { - return TreeView_Expand(m_hWindow, hItem, bExpand ? TVE_EXPAND : TVE_COLLAPSE); -} - -UINT TreeView::GetCheckState(HTREEITEM hItem) { - return TreeView_GetCheckState(m_hWindow, hItem); -} - -UINT TreeView::GetCount() { - return TreeView_GetCount(m_hWindow); -} - -BOOL TreeView::GetItem(LPTVITEM pItem) { - return TreeView_GetItem(m_hWindow, pItem); -} - -LPARAM TreeView::GetItemData(HTREEITEM hItem) { - TVITEM tvi = {0}; - tvi.mask = TVIF_PARAM; - tvi.hItem = hItem; - TreeView_GetItem(m_hWindow, &tvi); - - return tvi.lParam; -} - -HTREEITEM TreeView::GetSelection() { - return TreeView_GetSelection(m_hWindow); -} - -HTREEITEM TreeView::HitTest(LPTVHITTESTINFO lpht, bool bGetCursorPos) { - if (bGetCursorPos) { - GetCursorPos(&lpht->pt); - ScreenToClient(m_hWindow, &lpht->pt); - } - return TreeView_HitTest(m_hWindow, lpht); -} - -HTREEITEM TreeView::InsertItem(LPCWSTR pszText, int iImage, LPARAM lParam, HTREEITEM htiParent, HTREEITEM hInsertAfter) { - TVITEM tvi = {0}; - tvi.mask = TVIF_TEXT | TVIF_PARAM; - tvi.pszText = (LPWSTR)pszText; - tvi.lParam = lParam; - - if (iImage > -1) { - tvi.mask |= TVIF_IMAGE | TVIF_SELECTEDIMAGE; - tvi.iImage = iImage; - tvi.iSelectedImage = iImage; - } - - TVINSERTSTRUCT tvis = {0}; - tvis.item = tvi; - tvis.hInsertAfter = hInsertAfter; - tvis.hParent = htiParent; - - return TreeView_InsertItem(m_hWindow, &tvis); -} - -BOOL TreeView::SelectItem(HTREEITEM hItem) { - return TreeView_Select(m_hWindow, hItem, TVGN_CARET); -} - -UINT TreeView::SetCheckState(HTREEITEM hItem, BOOL fCheck) { - TVITEM tvi = {0}; - tvi.mask = TVIF_HANDLE | TVIF_STATE; - tvi.hItem = hItem; - tvi.stateMask = TVIS_STATEIMAGEMASK; - tvi.state = INDEXTOSTATEIMAGEMASK(fCheck + 1); - - return TreeView_SetItem(m_hWindow, &tvi); -} - -HIMAGELIST TreeView::SetImageList(HIMAGELIST himl, INT iImage) { - return TreeView_SetImageList(m_hWindow, himl, iImage); -} - -BOOL TreeView::SetItem(HTREEITEM hItem, LPCWSTR pszText) { - TVITEM tvi = {0}; - tvi.mask = TVIF_HANDLE; - tvi.hItem = hItem; - - if (!TreeView_GetItem(m_hWindow, &tvi)) - return FALSE; - - tvi.mask |= TVIF_TEXT; - tvi.pszText = (LPWSTR)pszText; - return TreeView_SetItem(m_hWindow, &tvi); -} - -int TreeView::SetItemHeight(SHORT cyItem) { - return TreeView_SetItemHeight(m_hWindow, cyItem); -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_dialog.cpp b/win32/win_dialog.cpp deleted file mode 100644 index 0b30b13be..000000000 --- a/win32/win_dialog.cpp +++ /dev/null @@ -1,367 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_dialog.h" -#include "win_taskbar.h" - -namespace win32 { - -// ============================================================================= - -Dialog::Dialog() : - m_bModal(true), m_iSnapGap(0) -{ - m_SizeLast.cx = 0; m_SizeLast.cy = 0; - m_SizeMax.cx = 0; m_SizeMax.cy = 0; - m_SizeMin.cx = 0; m_SizeMin.cy = 0; - m_PosLast.x = 0; m_PosLast.y = 0; -} - -Dialog::~Dialog() { - EndDialog(0); -} - -INT_PTR Dialog::Create(UINT uResourceID, HWND hParent, bool bModal) { - m_bModal = bModal; - if (bModal) { - INT_PTR nResult = ::DialogBoxParam(m_hInstance, MAKEINTRESOURCE(uResourceID), - hParent, DialogProcStatic, reinterpret_cast(this)); - m_hWindow = NULL; - return nResult; - } else { - m_hWindow = ::CreateDialogParam(m_hInstance, MAKEINTRESOURCE(uResourceID), - hParent, DialogProcStatic, reinterpret_cast(this)); - return reinterpret_cast(m_hWindow); - } -} - -void Dialog::EndDialog(INT_PTR nResult) { - // Remember last position and size - RECT rect; GetWindowRect(&rect); - m_PosLast.x = rect.left; - m_PosLast.y = rect.top; - m_SizeLast.cx = rect.right - rect.left; - m_SizeLast.cy = rect.bottom - rect.top; - - // Destroy window - if (::IsWindow(m_hWindow)) { - if (m_bModal) { - ::EndDialog(m_hWindow, nResult); - } else { - Destroy(); - } - } - m_hWindow = NULL; -} - -void Dialog::SetSizeMax(LONG cx, LONG cy) { - m_SizeMax.cx = cx; m_SizeMax.cy = cy; -} - -void Dialog::SetSizeMin(LONG cx, LONG cy) { - m_SizeMin.cx = cx; m_SizeMin.cy = cy; -} - -void Dialog::SetSnapGap(int iSnapGap) { - m_iSnapGap = iSnapGap; -} - -// ============================================================================= - -void Dialog::SetMinMaxInfo(LPMINMAXINFO lpMMI) { - if (m_SizeMax.cx > 0) lpMMI->ptMaxTrackSize.x = m_SizeMax.cx; - if (m_SizeMax.cy > 0) lpMMI->ptMaxTrackSize.y = m_SizeMax.cy; - if (m_SizeMin.cx > 0) lpMMI->ptMinTrackSize.x = m_SizeMin.cx; - if (m_SizeMin.cy > 0) lpMMI->ptMinTrackSize.y = m_SizeMin.cy; -} - -void Dialog::SnapToEdges(LPWINDOWPOS lpWndPos) { - if (m_iSnapGap == 0) return; - - HMONITOR hMonitor; - MONITORINFO mi; - RECT mon; - - // Get monitor info - SystemParametersInfo(SPI_GETWORKAREA, NULL, &mon, NULL); - if (GetSystemMetrics(SM_CMONITORS) > 1) { - hMonitor = MonitorFromWindow(m_hWindow, MONITOR_DEFAULTTONEAREST); - if (hMonitor) { - mi.cbSize = sizeof(mi); - GetMonitorInfo(hMonitor, &mi); - mon = mi.rcWork; - } - } - // Snap X axis - if (abs(lpWndPos->x - mon.left) <= m_iSnapGap) { - lpWndPos->x = mon.left; - } else if (abs(lpWndPos->x + lpWndPos->cx - mon.right) <= m_iSnapGap) { - lpWndPos->x = mon.right - lpWndPos->cx; - } - // Snap Y axis - if (abs(lpWndPos->y - mon.top) <= m_iSnapGap) { - lpWndPos->y = mon.top; - } else if (abs(lpWndPos->y + lpWndPos->cy - mon.bottom) <= m_iSnapGap) { - lpWndPos->y = mon.bottom - lpWndPos->cy; - } -} - -// ============================================================================= - -void Dialog::OnCancel() { - EndDialog(IDCANCEL); -} - -BOOL Dialog::OnClose() { - return FALSE; -} - -BOOL Dialog::OnInitDialog() { - return TRUE; -} - -void Dialog::OnOK() { - EndDialog(IDOK); -} - -// Superclassing -void Dialog::RegisterDlgClass(LPCWSTR lpszClassName) { - WNDCLASS wc; - ::GetClassInfo(m_hInstance, WC_DIALOG, &wc); // WC_DIALOG is #32770 - wc.lpszClassName = lpszClassName; - ::RegisterClass(&wc); -} - -// ============================================================================= - -BOOL Dialog::AddComboString(int nIDDlgItem, LPCWSTR lpString) { - return ::SendDlgItemMessage(m_hWindow, nIDDlgItem, CB_ADDSTRING, 0, reinterpret_cast(lpString)); -} - -BOOL Dialog::CheckDlgButton(int nIDButton, UINT uCheck) { - return ::CheckDlgButton(m_hWindow, nIDButton, uCheck); -} - -BOOL Dialog::CheckRadioButton(int nIDFirstButton, int nIDLastButton, int nIDCheckButton) { - return ::CheckRadioButton(m_hWindow, nIDFirstButton, nIDLastButton, nIDCheckButton); -} - -BOOL Dialog::EnableDlgItem(int nIDDlgItem, BOOL bEnable) { - return ::EnableWindow(::GetDlgItem(m_hWindow, nIDDlgItem), bEnable); -} - -INT Dialog::GetCheckedRadioButton(int nIDFirstButton, int nIDLastButton) { - for (int i = 0; i <= nIDLastButton - nIDFirstButton; i++) { - if (::IsDlgButtonChecked(m_hWindow, nIDFirstButton + i)) { - return i; - } - } - return 0; -} - -INT Dialog::GetComboSelection(int nIDDlgItem) { - return ::SendDlgItemMessage(m_hWindow, nIDDlgItem, CB_GETCURSEL, 0, 0); -} - -HWND Dialog::GetDlgItem(int nIDDlgItem) { - return ::GetDlgItem(m_hWindow, nIDDlgItem); -} - -UINT Dialog::GetDlgItemInt(int nIDDlgItem) { - return ::GetDlgItemInt(m_hWindow, nIDDlgItem, NULL, TRUE); -} - -void Dialog::GetDlgItemText(int nIDDlgItem, LPWSTR lpString, int cchMax) { - ::GetDlgItemText(m_hWindow, nIDDlgItem, lpString, cchMax); -} - -void Dialog::GetDlgItemText(int nIDDlgItem, wstring& str) { - int len = ::GetWindowTextLength(::GetDlgItem(m_hWindow, nIDDlgItem)) + 1; - vector buffer(len); - ::GetDlgItemText(m_hWindow, nIDDlgItem, &buffer[0], len); - str.assign(&buffer[0]); -} - -BOOL Dialog::HideDlgItem(int nIDDlgItem) { - return ::ShowWindow(::GetDlgItem(m_hWindow, nIDDlgItem), SW_HIDE); -} - -BOOL Dialog::IsDlgButtonChecked(int nIDButton) { - return ::IsDlgButtonChecked(m_hWindow, nIDButton); -} - -BOOL Dialog::SendDlgItemMessage(int nIDDlgItem, UINT uMsg, WPARAM wParam, LPARAM lParam) { - return ::SendDlgItemMessage(m_hWindow, nIDDlgItem, uMsg, wParam, lParam); -} - -BOOL Dialog::SetComboSelection(int nIDDlgItem, int iIndex) { - return ::SendDlgItemMessage(m_hWindow, nIDDlgItem, CB_SETCURSEL, iIndex, 0); -} - -BOOL Dialog::SetDlgItemText(int nIDDlgItem, LPCWSTR lpString) { - return ::SetDlgItemText(m_hWindow, nIDDlgItem, lpString); -} - -BOOL Dialog::ShowDlgItem(int nIDDlgItem, int nCmdShow) { - return ::ShowWindow(::GetDlgItem(m_hWindow, nIDDlgItem), nCmdShow); -} - -// ============================================================================= - -INT_PTR CALLBACK Dialog::DialogProcStatic(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - Dialog* window = reinterpret_cast(WindowMap.GetWindow(hwnd)); - if (!window && uMsg == WM_INITDIALOG) { - window = reinterpret_cast(lParam); - if (window) { - window->SetWindowHandle(hwnd); - WindowMap.Add(hwnd, window); - } - } - if (window) { - return window->DialogProc(hwnd, uMsg, wParam, lParam); - } else { - return FALSE; - } -} - -INT_PTR Dialog::DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - return DialogProcDefault(hwnd, uMsg, wParam, lParam); -} - -INT_PTR Dialog::DialogProcDefault(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_CLOSE: { - return OnClose(); - } - case WM_COMMAND: { - switch (LOWORD(wParam)) { - case IDOK: - OnOK(); - return TRUE; - case IDCANCEL: - OnCancel(); - return TRUE; - default: - return OnCommand(wParam, lParam); - } - break; - } - case WM_DESTROY: { - OnDestroy(); - break; - } - case WM_INITDIALOG: { - /*if (m_PosLast.x && m_PosLast.y) { - SetPosition(NULL, m_PosLast.x, m_PosLast.y, 0, 0, SWP_NOSIZE); - } - if (m_SizeLast.cx && m_SizeLast.cy) { - SetPosition(NULL, 0, 0, m_SizeLast.cx, m_SizeLast.cy, SWP_NOMOVE); - }*/ - return OnInitDialog(); - } - case WM_DROPFILES: { - OnDropFiles(reinterpret_cast(wParam)); - break; - } - case WM_ENTERSIZEMOVE: - case WM_EXITSIZEMOVE: { - SIZE size = {0}; - OnSize(uMsg, 0, size); - break; - } - case WM_GETMINMAXINFO: { - LPMINMAXINFO lpMMI = reinterpret_cast(lParam); - SetMinMaxInfo(lpMMI); - OnGetMinMaxInfo(lpMMI); - break; - } - case WM_LBUTTONDOWN: - case WM_MBUTTONDOWN: - case WM_RBUTTONDOWN: - case WM_LBUTTONUP: - case WM_MBUTTONUP: - case WM_RBUTTONUP: - case WM_MOUSEACTIVATE: - case WM_MOUSEHOVER: - case WM_MOUSEHWHEEL: - case WM_MOUSELEAVE: - case WM_MOUSEMOVE: - case WM_MOUSEWHEEL: { - LRESULT lResult = OnMouseEvent(uMsg, wParam, lParam); - if (lResult != -1) { - ::SetWindowLongPtrW(hwnd, DWL_MSGRESULT, lResult); - return TRUE; - } - break; - } - case WM_MOVE: { - POINTS pts = MAKEPOINTS(lParam); - OnMove(&pts); - break; - } - case WM_NOTIFY: { - LRESULT lResult = OnNotify(wParam, reinterpret_cast(lParam)); - if (lResult) { - ::SetWindowLongPtr(hwnd, DWL_MSGRESULT, lResult); - return TRUE; - } - break; - } - case WM_PAINT: { - if (::GetUpdateRect(hwnd, NULL, FALSE)) { - PAINTSTRUCT ps; - HDC hdc = ::BeginPaint(hwnd, &ps); - OnPaint(hdc, &ps); - ::EndPaint(hwnd, &ps); - } else { - HDC hdc = ::GetDC(hwnd); - OnPaint(hdc, NULL); - ::ReleaseDC(hwnd, hdc); - } - break; - } - case WM_SIZE: { - SIZE size = {LOWORD(lParam), HIWORD(lParam)}; - OnSize(uMsg, static_cast(wParam), size); - break; - } - case WM_TIMER: { - OnTimer(static_cast(wParam)); - break; - } - case WM_WINDOWPOSCHANGING: { - LPWINDOWPOS lpWndPos = reinterpret_cast(lParam); - SnapToEdges(lpWndPos); - OnWindowPosChanging(lpWndPos); - break; - } - default: { - if (uMsg == WM_TASKBARCREATED || - uMsg == WM_TASKBARBUTTONCREATED || - uMsg == WM_TASKBARCALLBACK) { - OnTaskbarCallback(uMsg, lParam); - return FALSE; - } - break; - } - } - - return FALSE; -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_dialog.h b/win32/win_dialog.h deleted file mode 100644 index 88087d49c..000000000 --- a/win32/win_dialog.h +++ /dev/null @@ -1,81 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef WIN_DIALOG_H -#define WIN_DIALOG_H - -#include "win_main.h" -#include "win_window.h" - -namespace win32 { - -// ============================================================================= - -class Dialog : public Window { -public: - Dialog(); - virtual ~Dialog(); - - virtual INT_PTR Create(UINT uResourceID, HWND hParent = NULL, bool bModal = true); - virtual void EndDialog(INT_PTR nResult); - virtual void SetSizeMax(LONG cx, LONG cy); - virtual void SetSizeMin(LONG cx, LONG cy); - virtual void SetSnapGap(int iSnapGap); - - virtual BOOL AddComboString(int nIDDlgItem, LPCWSTR lpString); - virtual BOOL CheckDlgButton(int nIDButton, UINT uCheck); - virtual BOOL CheckRadioButton(int nIDFirstButton, int nIDLastButton, int nIDCheckButton); - virtual BOOL EnableDlgItem(int nIDDlgItem, BOOL bEnable); - virtual INT GetCheckedRadioButton(int nIDFirstButton, int nIDLastButton); - virtual INT GetComboSelection(int nIDDlgItem); - virtual HWND GetDlgItem(int nIDDlgItem); - virtual UINT GetDlgItemInt(int nIDDlgItem); - virtual void GetDlgItemText(int nIDDlgItem, LPWSTR lpString, int cchMax = MAX_PATH); - virtual void GetDlgItemText(int nIDDlgItem, wstring& str); - virtual BOOL HideDlgItem(int nIDDlgItem); - virtual BOOL IsDlgButtonChecked(int nIDButton); - virtual BOOL SendDlgItemMessage(int nIDDlgItem, UINT uMsg, WPARAM wParam, LPARAM lParam); - virtual BOOL SetComboSelection(int nIDDlgItem, int iIndex); - virtual BOOL SetDlgItemText(int nIDDlgItem, LPCWSTR lpString); - virtual BOOL ShowDlgItem(int nIDDlgItem, int nCmdShow = SW_SHOWNORMAL); - -protected: - virtual void OnCancel(); - virtual BOOL OnClose(); - virtual BOOL OnInitDialog(); - virtual void OnOK(); - virtual void RegisterDlgClass(LPCWSTR lpszClassName); - - virtual INT_PTR DialogProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - virtual INT_PTR DialogProcDefault(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - -private: - static INT_PTR CALLBACK DialogProcStatic(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - - void SetMinMaxInfo(LPMINMAXINFO lpMMI); - void SnapToEdges(LPWINDOWPOS lpWndPos); - - bool m_bModal; - int m_iSnapGap; - SIZE m_SizeLast, m_SizeMax, m_SizeMin; - POINT m_PosLast; -}; - -} // namespace win32 - -#endif // WIN_DIALOG_H \ No newline at end of file diff --git a/win32/win_gdi.cpp b/win32/win_gdi.cpp deleted file mode 100644 index 3a9a80d2e..000000000 --- a/win32/win_gdi.cpp +++ /dev/null @@ -1,238 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_gdi.h" - -namespace win32 { - -// ============================================================================= - -Dc::Dc(HDC hDC) : - m_hDC(hDC), m_hBitmapOld(NULL), m_hBrushOld(NULL), m_hFontOld(NULL) -{ -} - -Dc::~Dc() { - if (m_hDC) { - if (m_hBitmapOld) ::DeleteObject(::SelectObject(m_hDC, m_hBitmapOld)); - if (m_hBrushOld) ::DeleteObject(::SelectObject(m_hDC, m_hBrushOld)); - if (m_hFontOld) ::DeleteObject(::SelectObject(m_hDC, m_hFontOld)); - - HWND hwnd = ::WindowFromDC(m_hDC); - if (hwnd) { - ::ReleaseDC(hwnd, m_hDC); - } else { - ::DeleteDC(m_hDC); - } - } -} - -void Dc::AttachDC(HDC hDC) { - if (m_hDC || !hDC) return; - m_hDC = hDC; -} - -HDC Dc::DetachDC() { - if (!m_hDC) return NULL; - - if (m_hBitmapOld) ::DeleteObject(::SelectObject(m_hDC, m_hBitmapOld)); - if (m_hBrushOld) ::DeleteObject(::SelectObject(m_hDC, m_hBrushOld)); - if (m_hFontOld) ::DeleteObject(::SelectObject(m_hDC, m_hFontOld)); - - HDC hDC = m_hDC; - m_hDC = NULL; - return hDC; -} - -HDC Dc::Get() const { - return m_hDC; -} - -void Dc::AttachBrush(HBRUSH hBrush) { - if (!m_hDC || !hBrush) return; - if (m_hBrushOld) ::DeleteObject(::SelectObject(m_hDC, m_hBrushOld)); - m_hBrushOld = (HBRUSH)::SelectObject(m_hDC, hBrush); -} - -void Dc::CreateSolidBrush(COLORREF color) { - if (!m_hDC) return; - if (m_hBrushOld) ::DeleteObject(::SelectObject(m_hDC, m_hBrushOld)); - HBRUSH hBrush = ::CreateSolidBrush(color); - m_hBrushOld = (HBRUSH)::SelectObject(m_hDC, hBrush); -} - -HBRUSH Dc::DetachBrush() { - if (!m_hDC || !m_hBrushOld) return NULL; - HBRUSH hBrush = (HBRUSH)::SelectObject(m_hDC, m_hBrushOld); - m_hBrushOld = NULL; - return hBrush; -} - -void Dc::AttachFont(HFONT hFont) { - if (!m_hDC || !hFont) return; - if (m_hFontOld) ::DeleteObject(::SelectObject(m_hDC, m_hFontOld)); - m_hFontOld = (HFONT)::SelectObject(m_hDC, hFont); -} - -HFONT Dc::DetachFont() { - if (!m_hDC || !m_hFontOld) return NULL; - HFONT hFont = (HFONT)::SelectObject(m_hDC, m_hFontOld); - m_hFontOld = NULL; - return hFont; -} - -void Dc::EditFont(LPCWSTR lpFaceName, INT iSize, BOOL bBold, BOOL bItalic, BOOL bUnderline) { - if (m_hFontOld) ::DeleteObject(::SelectObject(m_hDC, m_hFontOld)); - m_hFontOld = (HFONT)::GetCurrentObject(m_hDC, OBJ_FONT); - LOGFONT lFont; ::GetObject(m_hFontOld, sizeof(LOGFONT), &lFont); - - if (lpFaceName) - ::lstrcpy(lFont.lfFaceName, lpFaceName); - if (iSize > -1) { - lFont.lfHeight = -MulDiv(iSize, GetDeviceCaps(m_hDC, LOGPIXELSY), 72); - lFont.lfWidth = 0; - } - if (bBold > -1) - lFont.lfWeight = bBold ? FW_BOLD : FW_NORMAL; - if (bItalic > -1) - lFont.lfItalic = bItalic; - if (bUnderline > -1) - lFont.lfUnderline = bUnderline; - - HFONT hFont = ::CreateFontIndirect(&lFont); - ::SelectObject(m_hDC, hFont); -} - -BOOL Dc::FillRect(const RECT& rc, HBRUSH hbr) const { - return (BOOL)::FillRect(m_hDC, &rc, hbr); -} - -void Dc::FillRect(const RECT& rc, COLORREF color) const { - COLORREF old_color = ::SetBkColor(m_hDC, color); - ::ExtTextOut(m_hDC, 0, 0, ETO_OPAQUE, &rc, L"", 0, 0); - ::SetBkColor(m_hDC, old_color); -} - -void Dc::AttachBitmap(HBITMAP hBitmap) { - if (!m_hDC || !hBitmap) return; - if (m_hBitmapOld) ::DeleteObject(::SelectObject(m_hDC, m_hBitmapOld)); - m_hBitmapOld = (HBITMAP)::SelectObject(m_hDC, hBitmap); -} - -HBITMAP Dc::DetachBitmap() { - if (!m_hDC || !m_hBitmapOld) return NULL; - HBITMAP hBitmap = (HBITMAP)::SelectObject(m_hDC, m_hBitmapOld); - m_hBitmapOld = NULL; - return hBitmap; -} - -BOOL Dc::BitBlt(int x, int y, int nWidth, int nHeight, HDC hSrcDC, int xSrc, int ySrc, DWORD dwRop) const { - return ::BitBlt(m_hDC, x, y, nWidth, nHeight, hSrcDC, xSrc, ySrc, dwRop); -} - -int Dc::SetStretchBltMode(int mode) { - return ::SetStretchBltMode(m_hDC, mode); -} - -BOOL Dc::StretchBlt(int x, int y, int nWidth, int nHeight, HDC hSrcDC, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, DWORD dwRop) const { - return ::StretchBlt(m_hDC, x, y, nWidth, nHeight, hSrcDC, xSrc, ySrc, nSrcWidth, nSrcHeight, dwRop); -} - -int Dc::DrawText(LPCWSTR lpszString, int nCount, const RECT& rc, UINT nFormat) const { - return ::DrawText(m_hDC, lpszString, nCount, (LPRECT)&rc, nFormat); -} - -COLORREF Dc::GetTextColor() const { - return ::GetTextColor(m_hDC); -} - -COLORREF Dc::SetBkColor(COLORREF crColor) const { - return ::SetBkColor(m_hDC, crColor); -} - -int Dc::SetBkMode(int iBkMode) const { - return ::SetBkMode(m_hDC, iBkMode); -} - -COLORREF Dc::SetTextColor(COLORREF crColor) const { - return ::SetTextColor(m_hDC, crColor); -} - -// ============================================================================= - -Rect::Rect() { - left = 0; top = 0; right = 0; bottom = 0; -} - -Rect::Rect(int l, int t, int r, int b) { - left = l; top = t; right = r; bottom = b; -} - -Rect::Rect(const RECT& rc) { - ::CopyRect(this, &rc); -} - -Rect::Rect(LPCRECT lprc) { - ::CopyRect(this, lprc); -} - -void Rect::Copy(const RECT& rc) { - ::CopyRect(this, &rc); -} - -BOOL Rect::Equal(const RECT& rc) { - return ::EqualRect(&rc, this); -} - -BOOL Rect::Inflate(int dx, int dy) { - return ::InflateRect(this, dx, dy); -} - -BOOL Rect::Intersect(const RECT& rc1, const RECT& rc2) { - return ::IntersectRect(this, &rc1, &rc2); -} - -BOOL Rect::IsEmpty() { - return ::IsRectEmpty(this); -} - -BOOL Rect::Offset(int dx, int dy) { - return ::OffsetRect(this, dx, dy); -} - -BOOL Rect::PtIn(POINT pt) { - return ::PtInRect(this, pt); -} - -BOOL Rect::Set(int left, int top, int right, int bottom) { - return ::SetRect(this, left, top, right, bottom); -} - -BOOL Rect::SetEmpty() { - return ::SetRectEmpty(this); -} - -BOOL Rect::Subtract(const RECT& rc1, const RECT& rc2) { - return ::SubtractRect(this, &rc1, &rc2); -} - -BOOL Rect::Union(const RECT& rc1, const RECT& rc2) { - return ::UnionRect(this, &rc1, &rc2); -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_gdi.h b/win32/win_gdi.h deleted file mode 100644 index 4b97e3e02..000000000 --- a/win32/win_gdi.h +++ /dev/null @@ -1,117 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef WIN_GDI_H -#define WIN_GDI_H - -#include "win_main.h" - -namespace win32 { - -// ============================================================================= - -class Dc { -public: - Dc(HDC hDC = NULL); - virtual ~Dc(); - - void operator = (const HDC hDC) { - AttachDC(hDC); - } - - void AttachDC(HDC hDC); - HDC DetachDC(); - HDC Get() const; - - // Brush - void AttachBrush(HBRUSH hBrush); - void CreateSolidBrush(COLORREF color); - HBRUSH DetachBrush(); - - // Font - void AttachFont(HFONT hFont); - HFONT DetachFont(); - void EditFont(LPCWSTR lpFaceName = NULL, INT iSize = -1, BOOL bBold = -1, BOOL bItalic = -1, BOOL bUnderline = -1); - - // Painting - BOOL FillRect(const RECT& rc, HBRUSH hbr) const; - void FillRect(const RECT& rc, COLORREF color) const; - - // Bitmap - void AttachBitmap(HBITMAP hBitmap); - BOOL BitBlt(int x, int y, int nWidth, int nHeight, HDC hSrcDC, int xSrc, int ySrc, DWORD dwRop) const; - HBITMAP DetachBitmap(); - int SetStretchBltMode(int mode); - BOOL StretchBlt(int x, int y, int nWidth, int nHeight, HDC hSrcDC, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, DWORD dwRop) const; - - // Text - int DrawText(LPCWSTR lpszString, int nCount, const RECT& rc, UINT nFormat) const; - COLORREF GetTextColor() const; - COLORREF SetBkColor(COLORREF crColor) const; - int SetBkMode(int iBkMode) const; - COLORREF SetTextColor(COLORREF crColor) const; - -private: - HDC m_hDC; - HBITMAP m_hBitmapOld; - HBRUSH m_hBrushOld; - HFONT m_hFontOld; -}; - -// ============================================================================= - -class Rect : public RECT { -public: - Rect(); - Rect(int l, int t, int r, int b); - Rect(const RECT& rc); - Rect(LPCRECT lprc); - - BOOL operator == (const RECT& rc) { - return ::EqualRect(this, &rc); - } - BOOL operator != (const RECT& rc) { - return !::EqualRect(this, &rc); - } - void operator = (const RECT& srcRect) { - ::CopyRect(this, &srcRect); - } - - int Height() const { - return bottom - top; - } - int Width() const { - return right - left; - } - - void Copy(const RECT& rc); - BOOL Equal(const RECT& rc); - BOOL Inflate(int dx, int dy); - BOOL Intersect(const RECT& rc1, const RECT& rc2); - BOOL IsEmpty(); - BOOL Offset(int dx, int dy); - BOOL PtIn(POINT pt); - BOOL Set(int left, int top, int right, int bottom); - BOOL SetEmpty(); - BOOL Subtract(const RECT& rc1, const RECT& rc2); - BOOL Union(const RECT& rc1, const RECT& rc2); -}; - -} // namespace win32 - -#endif // WIN_GDI_H \ No newline at end of file diff --git a/win32/win_gdiplus.cpp b/win32/win_gdiplus.cpp deleted file mode 100644 index 3770f5573..000000000 --- a/win32/win_gdiplus.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_gdiplus.h" -#include - -#pragma comment(lib, "gdiplus.lib") - -using namespace Gdiplus; - -namespace win32 { - -// ============================================================================= - -GdiPlus::GdiPlus() { - Gdiplus::GdiplusStartupInput input; - Gdiplus::GdiplusStartup(&m_Token, &input, NULL); -} - -GdiPlus::~GdiPlus() { - Gdiplus::GdiplusShutdown(m_Token); - m_Token = NULL; -} - -void GdiPlus::DrawRectangle(const HDC hdc, const RECT& rect, Gdiplus::ARGB color) { - const Gdiplus::SolidBrush brush = Gdiplus::Color(color); - Gdiplus::Graphics graphics(hdc); - graphics.FillRectangle(&brush, - rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); -} - -HICON GdiPlus::LoadIcon(const wstring& file) { - HICON hIcon = NULL; - - Gdiplus::Bitmap* pBitmap = Gdiplus::Bitmap::FromFile(file.c_str()); - if (pBitmap) { - pBitmap->GetHICON(&hIcon); - delete pBitmap; pBitmap = NULL; - } - - return hIcon; -} - -HBITMAP GdiPlus::LoadImage(const wstring& file) { - HBITMAP hBitmap = NULL; - - Gdiplus::Bitmap* pBitmap = Gdiplus::Bitmap::FromFile(file.c_str()); - if (pBitmap) { - pBitmap->GetHBITMAP(NULL, &hBitmap); - delete pBitmap; pBitmap = NULL; - } - - return hBitmap; -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_http.cpp b/win32/win_http.cpp deleted file mode 100644 index 8a405f401..000000000 --- a/win32/win_http.cpp +++ /dev/null @@ -1,525 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_http.h" -#include "../common.h" -#include "../string.h" - -namespace win32 { - -// ============================================================================= - -Url& Url::operator=(const Url& url) { - Scheme = url.Scheme; - Host = url.Host; - Path = url.Path; - - return *this; -} - -void Url::operator=(const wstring& url) { - Crack(url); -} - -void Url::Crack(wstring url) { - // Get scheme - size_t i = url.find(L"://", 0); - if (i != wstring::npos) { - Scheme = url.substr(0, i); - url = url.substr(i + 3); - } else { - Scheme.clear(); - } - - // Get host and path - i = url.find(L"/", 0); - if (i == wstring::npos) i = url.length(); - Host = url.substr(0, i); - Path = url.substr(i); -} - -// ============================================================================= - -bool Http::Connect(wstring szServer, wstring szObject, wstring szData, wstring szVerb, wstring szHeader, - wstring szReferer, wstring szFile, DWORD dwClientMode, LPARAM lParam) { - // Close previous connection, if any - Cleanup(); - - // Set new information - m_OptionalData = ToANSI(szData); - m_dwClientMode = dwClientMode; - m_lParam = lParam; - m_File = szFile; - m_Referer = szReferer; - m_Verb = szVerb; - - // Prepare file location - if (!szFile.empty()) { - CreateFolder(GetPathOnly(szFile)); - } - - // Build request header - m_RequestHeader = szHeader; - wstring header = BuildRequestHeader(szHeader); - - // Last chance to modify things - OnInitialize(); - - // Create a session handle - m_hSession = ::WinHttpOpen(m_UserAgent.c_str(), - WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, - WINHTTP_NO_PROXY_NAME, - WINHTTP_NO_PROXY_BYPASS, - WINHTTP_FLAG_ASYNC); - if (!m_hSession) { - Cleanup(); - OnError(::GetLastError()); - return false; - } - - // Open an HTTP session - m_hConnect = ::WinHttpConnect(m_hSession, - szServer.c_str(), - m_HttpsEnabled ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT, - 0); - if (!m_hConnect) { - Cleanup(); - OnError(::GetLastError()); - return false; - } - - // Open a GET/POST request - m_hRequest = ::WinHttpOpenRequest(m_hConnect, - szVerb.c_str(), - szObject.c_str(), - NULL, - szReferer.c_str(), - WINHTTP_DEFAULT_ACCEPT_TYPES, - m_HttpsEnabled ? WINHTTP_FLAG_SECURE : 0); - if (!m_hRequest) { - Cleanup(); - OnError(::GetLastError()); - return false; - } - - // Setup proxy - if (!m_Proxy.empty()) { - WINHTTP_PROXY_INFO pi = {0}; - pi.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; - pi.lpszProxy = const_cast(m_Proxy.c_str()); - ::WinHttpSetOption(m_hRequest, WINHTTP_OPTION_PROXY, &pi, sizeof(pi)); - if (!m_ProxyUser.empty()) { - ::WinHttpSetOption(m_hRequest, WINHTTP_OPTION_PROXY_USERNAME, - (LPVOID)m_ProxyUser.c_str(), m_ProxyUser.size() * sizeof(wchar_t)); - if (!m_ProxyPass.empty()) { - ::WinHttpSetOption(m_hRequest, WINHTTP_OPTION_PROXY_PASSWORD, - (LPVOID)m_ProxyPass.c_str(), m_ProxyPass.size() * sizeof(wchar_t)); - } - } - } - - // Disable auto-redirect - if (m_AutoRedirect == FALSE) { - DWORD dwDisable = WINHTTP_DISABLE_REDIRECTS; - ::WinHttpSetOption(m_hRequest, WINHTTP_OPTION_DISABLE_FEATURE, &dwDisable, sizeof(dwDisable)); - } - - // Set security options - if (m_HttpsEnabled) { - DWORD dwOptions = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | - SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | - SECURITY_FLAG_IGNORE_UNKNOWN_CA; - ::WinHttpSetOption(m_hRequest, WINHTTP_OPTION_SECURITY_FLAGS, - &dwOptions, sizeof(DWORD)); - } - - // Install the status callback function - WINHTTP_STATUS_CALLBACK pCallback = ::WinHttpSetStatusCallback(m_hRequest, - reinterpret_cast(&Callback), - WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, - NULL); - if (pCallback != NULL) { - Cleanup(); - OnError(::GetLastError()); - return false; - } - - // Send the request - if (!::WinHttpSendRequest(m_hRequest, - header.c_str(), - header.length(), - (LPVOID)(m_OptionalData.c_str()), - m_OptionalData.length(), - m_OptionalData.length(), - reinterpret_cast(this))) { - Cleanup(); - OnError(::GetLastError()); - return false; - } - - // Success - return true; -} - -bool Http::Connect(const Url& url, wstring szData, wstring szVerb, wstring szHeader, wstring szReferer, - wstring szFile, DWORD dwClientMode, LPARAM lParam) { - return Connect(url.Host, url.Path, szData, szVerb, szHeader, szReferer, szFile, dwClientMode, lParam); -} - -bool Http::Get(wstring szServer, wstring szObject, wstring szFile, DWORD dwClientMode, LPARAM lParam) { - return Connect(szServer, szObject, L"", L"GET", L"", szServer, szFile, dwClientMode, lParam); -} - -bool Http::Get(const Url& url, wstring szFile, DWORD dwClientMode, LPARAM lParam) { - return Connect(url.Host, url.Path, L"", L"GET", L"", url.Host, szFile, dwClientMode, lParam); -} - -bool Http::Post(wstring szServer, wstring szObject, wstring szData, wstring szFile, DWORD dwClientMode, LPARAM lParam) { - return Connect(szServer, szObject, szData, L"POST", L"", szServer, szFile, dwClientMode, lParam); -} - -bool Http::Post(const Url& url, wstring szData, wstring szFile, DWORD dwClientMode, LPARAM lParam) { - return Connect(url.Host, url.Path, szData, L"POST", L"", url.Host, szFile, dwClientMode, lParam); -} - -// ============================================================================= - -void CALLBACK Http::Callback(HINTERNET hInternet, DWORD_PTR dwContext, DWORD dwInternetStatus, - LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) { - Http* object = reinterpret_cast(dwContext); - if (object != NULL) { - object->StatusCallback(hInternet, dwInternetStatus, - lpvStatusInformation, dwStatusInformationLength); - } -} - -void Http::StatusCallback(HINTERNET hInternet, DWORD dwInternetStatus, - LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) { - switch (dwInternetStatus) { - case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: { - if (::WinHttpReceiveResponse(hInternet, NULL)) { - OnSendRequestComplete(); - } - break; - } - - case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: { - DWORD dwSize = 0; - LPVOID lpOutBuffer = NULL; - if (!::WinHttpQueryHeaders(hInternet, - WINHTTP_QUERY_RAW_HEADERS_CRLF, - WINHTTP_HEADER_NAME_BY_INDEX, - NULL, &dwSize, WINHTTP_NO_HEADER_INDEX)) { - DWORD dwError = GetLastError(); - if (dwError != ERROR_INSUFFICIENT_BUFFER) { - OnError(dwError); - break; - } - } - lpOutBuffer = new WCHAR[dwSize]; - if (::WinHttpQueryHeaders(hInternet, - WINHTTP_QUERY_RAW_HEADERS_CRLF, - WINHTTP_HEADER_NAME_BY_INDEX, - lpOutBuffer, &dwSize, - WINHTTP_NO_HEADER_INDEX)) { - if (!ParseResponseHeader((LPWSTR)lpOutBuffer) || - OnHeadersAvailable(m_ResponseHeader)) { - delete [] lpOutBuffer; - break; - } - } - delete [] lpOutBuffer; - ::WinHttpQueryDataAvailable(hInternet, NULL); - break; - } - - case WINHTTP_CALLBACK_STATUS_REDIRECT: { - StatusCallback(hInternet, WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE, - lpvStatusInformation, dwStatusInformationLength); - break; - } - - case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: { - DWORD dwSize = *reinterpret_cast(lpvStatusInformation); - if (dwSize > 0) { - LPSTR lpOutBuffer = new char[dwSize + 1]; - ::ZeroMemory(lpOutBuffer, dwSize + 1); - if (!WinHttpReadData(hInternet, (LPVOID)lpOutBuffer, dwSize, NULL)) { - delete [] lpOutBuffer; - break; - } - if (OnDataAvailable()) { - Cleanup(); - } - } else if (dwSize == 0) { - if (m_ContentEncoding == HTTP_Encoding_Gzip) { - string input, output; - input.append(m_Buffer, m_dwDownloaded); - UncompressGzippedString(input, output); - if (!output.empty()) { - delete [] m_Buffer; - m_dwDownloaded = output.length(); - m_Buffer = new char[m_dwDownloaded]; - memcpy(m_Buffer, &output[0], m_dwDownloaded); - } - } - if (!m_File.empty()) { - HANDLE hFile = ::CreateFile(m_File.c_str(), GENERIC_WRITE, 0, NULL, - CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile != INVALID_HANDLE_VALUE) { - DWORD dwBytesToWrite = m_dwDownloaded, dwBytesWritten = 0; - ::WriteFile(hFile, (LPCVOID)m_Buffer, dwBytesToWrite, &dwBytesWritten, NULL); - ::CloseHandle(hFile); - } - } - if (!OnReadComplete()) { - Cleanup(); - } - } - break; - } - - case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: { - if (dwStatusInformationLength > 0) { - LPSTR lpReadBuffer = (LPSTR)lpvStatusInformation; - DWORD dwBytesRead = dwStatusInformationLength; - if(!m_Buffer) { - m_Buffer = lpReadBuffer; - } else { - LPSTR lpOldBuffer = m_Buffer; - m_Buffer = new char[m_dwDownloaded + dwBytesRead]; - memcpy(m_Buffer, lpOldBuffer, m_dwDownloaded); - memcpy(m_Buffer + m_dwDownloaded, lpReadBuffer, dwBytesRead); - delete [] lpOldBuffer; - delete [] lpReadBuffer; - } - m_dwDownloaded += dwBytesRead; - if (OnReadData()) { - Cleanup(); - } else { - ::WinHttpQueryDataAvailable(hInternet, NULL); - } - } - break; - } - - case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: { - WINHTTP_ASYNC_RESULT* result = (WINHTTP_ASYNC_RESULT*)lpvStatusInformation; - switch (result->dwResult) { - case API_RECEIVE_RESPONSE: - case API_QUERY_DATA_AVAILABLE: - case API_READ_DATA: - case API_WRITE_DATA: - case API_SEND_REQUEST: - OnError(result->dwError); - //Cleanup(); - } - break; - } - } -} - -// ============================================================================= - -Http::Http() : - m_AutoRedirect(TRUE), m_Buffer(NULL), m_ContentEncoding(HTTP_Encoding_None), m_HttpsEnabled(FALSE), - m_ResponseStatusCode(0), m_dwDownloaded(0), m_dwTotal(0), m_dwClientMode(0), m_lParam(0), - m_hConnect(NULL), m_hRequest(NULL), m_hSession(NULL) -{ - m_UserAgent = L"Mozilla/5.0"; -} - -Http::~Http() { - Cleanup(); -} - -void Http::Cleanup() { - // Close handles - if (m_hRequest) { - ::WinHttpSetStatusCallback(m_hRequest, NULL, NULL, NULL); - ::WinHttpCloseHandle(m_hRequest); - m_hRequest = NULL; - } - if (m_hConnect) { - ::WinHttpCloseHandle(m_hConnect); - m_hConnect = NULL; - } - if (m_hSession) { - ::WinHttpCloseHandle(m_hSession); - m_hSession = NULL; - } - - // Clear buffer - if (m_Buffer) { - delete [] m_Buffer; - m_Buffer = NULL; - } - - // Reset variables - m_ContentEncoding = HTTP_Encoding_None; - m_File.clear(); - m_OptionalData.clear(); - m_Referer.clear(); - m_RequestHeader.clear(); - m_ResponseStatusCode = 0; - m_ResponseHeader.clear(); - m_Verb.clear(); - m_dwDownloaded = 0; - m_dwTotal = 0; - m_dwClientMode = 0; - m_lParam = 0; -} - -void Http::ClearCookies() { - m_Cookie.clear(); -} - -wstring Http::GetCookie() { - return m_Cookie; -} - -DWORD Http::GetClientMode() { - return m_dwClientMode; -} - -wstring Http::GetData() { - if (!m_Buffer) return L""; - return ToUTF8(m_Buffer); -} - -wstring Http::GetDefaultHeader() { - return L"Content-Type: application/x-www-form-urlencoded\r\nAccept: */*\r\n"; -} - -LPARAM Http::GetParam() { - return m_lParam; -} - -int Http::GetResponseStatusCode() { - return m_ResponseStatusCode; -} - -void Http::SetAutoRedirect(BOOL enabled) { - m_AutoRedirect = enabled; -} - -void Http::SetHttpsEnabled(BOOL enabled) { - m_HttpsEnabled = enabled; -} - -void Http::SetProxy(const wstring& proxy, const wstring& user, const wstring& pass) { - m_Proxy = proxy; - m_ProxyUser = user; - m_ProxyPass = pass; -} - -void Http::SetUserAgent(const wstring& user_agent) { - m_UserAgent = user_agent; -} - -// ============================================================================= - -bool Http::ParseHeader(const wstring& text, http_header_t& header) { - header.clear(); - if (text.empty()) return false; - - vector header_list; - Split(text, L"\r\n", header_list); - - for (auto it = header_list.begin(); it != header_list.end(); ++it) { - int pos = InStr(*it, L":", 0); - if (pos == -1) { - if (StartsWith(*it, L"HTTP/")) { - m_ResponseStatusCode = ToInt(InStr(*it, L" ", L" ")); - } - } else { - wstring name = CharLeft(*it, pos); - wstring value = it->substr(pos + 2); - header.insert(std::pair(name, value)); - } - } - - return true; -} - -wstring Http::BuildRequestHeader(wstring header) { - if (header.empty()) { - header = GetDefaultHeader(); - } else { - header = header + L"\r\n"; - } - - if (!m_Cookie.empty()) { - header += L"Cookie: " + m_Cookie + L"\r\n"; - } - - return header; -} - -bool Http::ParseResponseHeader(const wstring& header) { - if (!ParseHeader(header, m_ResponseHeader)) return false; - Url location; - - for (auto it = m_ResponseHeader.cbegin(); it != m_ResponseHeader.cend(); ++it) { - wstring name = it->first; - wstring value = it->second; - - // Content-Encoding: - if (IsEqual(name, L"Content-Encoding")) { - if (InStr(value, L"gzip") > -1) { - m_ContentEncoding = HTTP_Encoding_Gzip; - } else { - m_ContentEncoding = HTTP_Encoding_None; - } - - // Content-Length: - } else if (IsEqual(name, L"Content-Length")) { - m_dwTotal = ToInt(value); - - // Location: - } else if (IsEqual(name, L"Location")) { - if (!OnRedirect(value)) { - location.Crack(value); - } - - // Set-Cookie: - } else if (IsEqual(name, L"Set-Cookie")) { - int pos = InStr(value, L";", 0); - if (pos > -1) value = value.substr(0, pos); - m_Cookie += (m_Cookie.empty() ? L"" : L"; ") + value; - } - } - - if (!location.Host.empty()) { - if (m_AutoRedirect == FALSE) { - if (!Connect(location, ToUTF8(m_OptionalData), m_Verb, m_RequestHeader, - m_Referer, m_File, m_dwClientMode, m_lParam)) { - Cleanup(); - } - return false; - } else { - m_ContentEncoding = HTTP_Encoding_None; - m_dwDownloaded = 0; - m_dwTotal = 0; - } - } - - return true; -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_http.h b/win32/win_http.h deleted file mode 100644 index 7e36cdca9..000000000 --- a/win32/win_http.h +++ /dev/null @@ -1,133 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef WIN_HTTP_H -#define WIN_HTTP_H - -#include "win_main.h" -#include -#include - -namespace win32 { - -enum HttpContentEncoding { - HTTP_Encoding_None = 0, - HTTP_Encoding_Gzip -}; - -typedef std::multimap http_header_t; - -// ============================================================================= - -class Url { -public: - Url() {} - Url(wstring url) { Crack(url); } - virtual ~Url() {} - - Url& operator=(const Url& url); - void operator=(const wstring& url); - -public: - void Crack(wstring url); - -public: - wstring Scheme, Host, Path; -}; - -// ============================================================================= - -class Http { -public: - Http(); - ~Http(); - - void Cleanup(); - void ClearCookies(); - wstring GetCookie(); - DWORD GetClientMode(); - wstring GetData(); - wstring GetDefaultHeader(); - LPARAM GetParam(); - int GetResponseStatusCode(); - void SetAutoRedirect(BOOL enabled); - void SetHttpsEnabled(BOOL enabled); - void SetProxy(const wstring& proxy, const wstring& user, const wstring& pass); - void SetUserAgent(const wstring& user_agent); - - bool Connect(wstring szServer, wstring szObject, wstring szData, wstring szVerb, - wstring szHeader, wstring szReferer, wstring szFile, - DWORD dwClientMode = 0, LPARAM lParam = 0); - bool Connect(const Url& url, wstring szData, wstring szVerb, - wstring szHeader, wstring szReferer, wstring szFile, - DWORD dwClientMode = 0, LPARAM lParam = 0); - bool Get(wstring szServer, wstring szObject, wstring szFile, - DWORD dwClientMode = 0, LPARAM lParam = 0); - bool Get(const Url& url, wstring szFile, - DWORD dwClientMode = 0, LPARAM lParam = 0); - bool Post(wstring szServer, wstring szObject, wstring szData, wstring szFile, - DWORD dwClientMode = 0, LPARAM lParam = 0); - bool Post(const Url& url, wstring szData, wstring szFile, - DWORD dwClientMode = 0, LPARAM lParam = 0); - - virtual void OnInitialize() {} - virtual BOOL OnError(DWORD dwError) { return FALSE; } - virtual BOOL OnSendRequestComplete() { return FALSE; } - virtual BOOL OnHeadersAvailable(http_header_t& headers) { return FALSE; } - virtual BOOL OnDataAvailable() { return FALSE; } - virtual BOOL OnReadData() { return FALSE; } - virtual BOOL OnReadComplete() { return TRUE; } - virtual BOOL OnRedirect(wstring address) { return FALSE; } - -private: - static void CALLBACK Callback(HINTERNET hInternet, DWORD_PTR dwContext, - DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength); - void StatusCallback(HINTERNET hInternet, - DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength); - -protected: - wstring BuildRequestHeader(wstring header); - bool ParseHeader(const wstring& text, http_header_t& header); - bool ParseResponseHeader(const wstring& header); - - BOOL m_AutoRedirect; - LPSTR m_Buffer; - INT m_ContentEncoding; - wstring m_Cookie; - wstring m_File; - BOOL m_HttpsEnabled; - string m_OptionalData; - wstring m_Proxy, m_ProxyUser, m_ProxyPass; - wstring m_Referer; - wstring m_UserAgent; - wstring m_Verb; - - wstring m_RequestHeader; - int m_ResponseStatusCode; - http_header_t m_ResponseHeader; - - DWORD m_dwDownloaded, m_dwTotal; - DWORD m_dwClientMode; - LPARAM m_lParam; - - HINTERNET m_hConnect, m_hRequest, m_hSession; -}; - -} // namespace win32 - -#endif // WIN_HTTP_H \ No newline at end of file diff --git a/win32/win_main.cpp b/win32/win_main.cpp deleted file mode 100644 index e7cda32d6..000000000 --- a/win32/win_main.cpp +++ /dev/null @@ -1,212 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_main.h" -#include "win_window.h" - -namespace win32 { - -class WindowMap WindowMap; - -// ============================================================================= - -void WindowMap::Add(HWND hWnd, Window* pWindow) { - if (hWnd != NULL) { - if (GetWindow(hWnd) == NULL) { - m_WindowMap.insert(std::make_pair(hWnd, pWindow)); - } - } -} - -void WindowMap::Clear() { - if (m_WindowMap.empty()) return; - for (WndMap::iterator i = m_WindowMap.begin(); i != m_WindowMap.end(); ++i) { - HWND hWnd = i->first; - if (::IsWindow(hWnd)) ::DestroyWindow(hWnd); - } - m_WindowMap.clear(); -} - -Window* WindowMap::GetWindow(HWND hWnd) { - if (m_WindowMap.empty()) return NULL; - WndMap::iterator i = m_WindowMap.find(hWnd); - if (i != m_WindowMap.end()) { - return i->second; - } else { - return NULL; - } -} - -void WindowMap::Remove(HWND hWnd) { - if (m_WindowMap.empty()) return; - for (WndMap::iterator i = m_WindowMap.begin(); i != m_WindowMap.end(); ++i) { - if (hWnd == i->first) { - m_WindowMap.erase(i); - return; - } - } -} - -void WindowMap::Remove(Window* pWindow) { - if (m_WindowMap.empty()) return; - for (WndMap::iterator i = m_WindowMap.begin(); i != m_WindowMap.end(); ++i) { - if (pWindow == i->second) { - m_WindowMap.erase(i); - return; - } - } -} - -// ============================================================================= - -App::App() : - m_VersionMajor(1), m_VersionMinor(0), m_VersionRevision(0) -{ - m_hInstance = ::GetModuleHandle(NULL); -} - -App::~App() { - WindowMap.Clear(); -} - -BOOL App::InitInstance() { - return TRUE; -} - -int App::MessageLoop() { - MSG msg; - while (::GetMessage(&msg, NULL, 0, 0)) { - BOOL processed = FALSE; - if ((msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST) || - (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST)) { - for (HWND hwnd = msg.hwnd; hwnd != NULL; hwnd = ::GetParent(hwnd)) { - Window* pWindow = WindowMap.GetWindow(hwnd); - if (pWindow) { - processed = pWindow->PreTranslateMessage(&msg); - if (processed) break; - } - } - } - if (!processed) { - ::TranslateMessage(&msg); - ::DispatchMessage(&msg); - } - } - return static_cast(LOWORD(msg.wParam)); -} - -void App::PostQuitMessage(int nExitCode) { - ::PostQuitMessage(nExitCode); -} - -int App::Run() { - if (InitInstance()) { - return MessageLoop(); - } else { - ::PostQuitMessage(-1); - return -1; - } -} - -void App::SetVersionInfo(int major, int minor, int revision) { - m_VersionMajor = major; - m_VersionMinor = minor; - m_VersionRevision = revision; -} - -wstring App::GetCurrentDirectory() { - WCHAR buff[MAX_PATH]; - DWORD len = ::GetCurrentDirectory(MAX_PATH, buff); - if (len > 0 && len < MAX_PATH) { - return buff; - } else { - return L""; - } -} - -HINSTANCE App::GetInstanceHandle() const { - return m_hInstance; -} - -wstring App::GetModulePath() { - WCHAR buff[MAX_PATH]; - ::GetModuleFileName(m_hInstance, buff, MAX_PATH); - return buff; -} - -BOOL App::InitCommonControls(DWORD dwFlags) { - INITCOMMONCONTROLSEX icc; - icc.dwSize = sizeof(INITCOMMONCONTROLSEX); - icc.dwICC = dwFlags; - return ::InitCommonControlsEx(&icc); -} - -BOOL App::SetCurrentDirectory(const wstring& directory) { - return ::SetCurrentDirectory(directory.c_str()); -} - -// ============================================================================= - -WinVersion GetWinVersion() { - static bool checked_version = false; - static WinVersion win_version = VERSION_PRE_XP; - if (!checked_version) { - OSVERSIONINFOEX version_info; - version_info.dwOSVersionInfoSize = sizeof(version_info); - GetVersionEx(reinterpret_cast(&version_info)); - if (version_info.dwMajorVersion == 5) { - switch (version_info.dwMinorVersion) { - case 0: - win_version = VERSION_PRE_XP; - break; - case 1: - win_version = VERSION_XP; - break; - case 2: - default: - win_version = VERSION_SERVER_2003; - break; - } - } else if (version_info.dwMajorVersion == 6) { - if (version_info.wProductType != VER_NT_WORKSTATION) { - win_version = VERSION_SERVER_2008; - } else { - switch (version_info.dwMinorVersion) { - case 0: - win_version = VERSION_VISTA; - break; - case 1: - win_version = VERSION_WIN7; - break; - case 2: - win_version = VERSION_WIN8; - break; - default: - win_version = VERSION_WIN8_1; - break; - } - } - } else if (version_info.dwMajorVersion > 6) { - win_version = VERSION_UNKNOWN; - } - checked_version = true; - } - return win_version; -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_main.h b/win32/win_main.h deleted file mode 100644 index 1f8431f23..000000000 --- a/win32/win_main.h +++ /dev/null @@ -1,113 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef WIN_MAIN_H -#define WIN_MAIN_H - -#include -#include -#include - -#include -#include -#include - -using std::string; -using std::vector; -using std::wstring; - -#ifndef GET_X_LPARAM -#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp)) -#endif -#ifndef GET_Y_LPARAM -#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp)) -#endif - -namespace win32 { - -class Window; - -// ============================================================================= - -/* Window map */ - -typedef std::map WndMap; - -class WindowMap { -public: - void Add(HWND hWnd, Window* pWindow); - void Clear(); - Window* GetWindow(HWND hWnd); - void Remove(HWND hWnd); - void Remove(Window* pWindow); - -private: - WndMap m_WindowMap; -}; - -extern class WindowMap WindowMap; - -// ============================================================================= - -/* Application */ - -class App { -public: - App(); - virtual ~App(); - - virtual BOOL InitInstance(); - virtual int MessageLoop(); - virtual void PostQuitMessage(int nExitCode = 0); - virtual int Run(); - - int GetVersionMajor() { return m_VersionMajor; } - int GetVersionMinor() { return m_VersionMinor; } - int GetVersionRevision() { return m_VersionRevision; } - void SetVersionInfo(int major, int minor, int revision); - - wstring GetCurrentDirectory(); - HINSTANCE GetInstanceHandle() const; - wstring GetModulePath(); - BOOL InitCommonControls(DWORD dwFlags); - BOOL SetCurrentDirectory(const wstring& directory); - -private: - HINSTANCE m_hInstance; - int m_VersionMajor, m_VersionMinor, m_VersionRevision; -}; - -// ============================================================================= - -enum WinVersion { - VERSION_PRE_XP = 0, - VERSION_XP, - VERSION_SERVER_2003, - VERSION_VISTA, - VERSION_SERVER_2008, - VERSION_WIN7, - VERSION_WIN8, - VERSION_WIN8_1, - VERSION_UNKNOWN -}; - -WinVersion GetWinVersion(); - -} // namespace win32 - -#endif // WIN_MAIN_H \ No newline at end of file diff --git a/win32/win_menu.cpp b/win32/win_menu.cpp deleted file mode 100644 index 55e9498b9..000000000 --- a/win32/win_menu.cpp +++ /dev/null @@ -1,199 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_menu.h" - -namespace win32 { - -// ============================================================================= - -HMENU MenuList::CreateNewMenu(LPCWSTR lpName, vector& hMenu) { - // Initialize - int menu_index = GetIndex(lpName); - if (menu_index == -1) return NULL; - int nMenu = hMenu.size(); - hMenu.resize(nMenu + 1); - - // Create menu - if (Menu[menu_index].Type == L"menubar") { - hMenu[nMenu] = ::CreateMenu(); - } else { - hMenu[nMenu] = ::CreatePopupMenu(); - } - if (!hMenu[nMenu]) return NULL; - - // Add items - for (unsigned int i = 0; i < Menu[menu_index].Items.size(); i++) { - UINT uFlags = (Menu[menu_index].Items[i].Checked ? MF_CHECKED : NULL) | - (Menu[menu_index].Items[i].Default ? MF_DEFAULT : NULL) | - (Menu[menu_index].Items[i].Enabled ? MF_ENABLED : MF_GRAYED) | - (Menu[menu_index].Items[i].NewColumn ? MF_MENUBARBREAK : NULL) | - (Menu[menu_index].Items[i].Radio ? MFT_RADIOCHECK : NULL); - - switch (Menu[menu_index].Items[i].Type) { - // Normal item - case MENU_ITEM_NORMAL: { - UINT_PTR uIDNewItem = (UINT_PTR)&Menu[menu_index].Items[i].Action; - ::AppendMenu(hMenu[nMenu], MF_STRING | uFlags, uIDNewItem, - Menu[menu_index].Items[i].Name.c_str()); - if (Menu[menu_index].Items[i].Default) { - ::SetMenuDefaultItem(hMenu[nMenu], uIDNewItem, FALSE); - } - // TEMP - /*Menu[menu_index].Items[i].Icon.Load(ImageList_GetIcon(m_hImageList, 1, ILD_NORMAL)); - BOOL bReturn = ::SetMenuItemBitmaps(hMenu[nMenu], uIDNewItem, MF_BITMAP | MF_BYCOMMAND, - Menu[menu_index].Items[i].Icon.Handle, NULL);*/ - break; - } - // Separator - case MENU_ITEM_SEPARATOR: { - ::AppendMenu(hMenu[nMenu], MF_SEPARATOR, NULL, NULL); - break; - } - // Sub menu - case MENU_ITEM_SUBMENU: { - int sub_index = GetIndex(Menu[menu_index].Items[i].SubMenu.c_str()); - if (sub_index > -1 && sub_index != menu_index) { - HMENU hSubMenu = CreateNewMenu(Menu[sub_index].Name.c_str(), hMenu); - ::AppendMenu(hMenu[nMenu], MF_POPUP | uFlags, - (UINT_PTR)hSubMenu, Menu[menu_index].Items[i].Name.c_str()); - } - break; - } - } - } - - return hMenu[nMenu]; -} - -wstring MenuList::Show(HWND hwnd, int x, int y, LPCWSTR lpName) { - // Create menu - vector hMenu; - CreateNewMenu(lpName, hMenu); - if (!hMenu.size()) return L""; - - // Set position - if (x == 0 && y == 0) { - POINT point; - ::GetCursorPos(&point); - x = point.x; y = point.y; - } - - // Show menu - UINT_PTR index = ::TrackPopupMenuEx(hMenu[0], - TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RETURNCMD, - x, y, hwnd, NULL); - - // Clean-up - for (unsigned int i = 0; i < hMenu.size(); i++) { - ::DestroyMenu(hMenu[i]); - } - /*for (int i = 1; i < Count; i++) { - for (int j = 1; j < Menu[i].Count; i++) { - Menu[i].Items[j].Icon.Destroy(); - } - }*/ - - // Return action - if (index > 0) { - wstring* str = reinterpret_cast(index); - return *str; - } else { - return L""; - } -} - -// ============================================================================= - -void MenuList::Create(LPCWSTR lpName, LPCWSTR lpType) { - Menu.resize(Menu.size() + 1); - Menu.back().Name = lpName; - Menu.back().Type = lpType; -} - -int MenuList::GetIndex(LPCWSTR lpName) { - for (unsigned int i = 0; i < Menu.size(); i++) { - if (Menu[i].Name == lpName) return i; - } - return -1; -} - -void MenuList::SetImageList(HIMAGELIST hImageList) { - m_hImageList = hImageList; -} - -// ============================================================================= - -void MenuList::Menu::CreateItem(wstring action, wstring name, wstring sub, - bool checked, bool def, bool enabled, - bool newcolumn, bool radio) { - unsigned int i = Items.size(); - Items.resize(i + 1); - - Items[i].Action = action; - Items[i].Checked = checked; - Items[i].Default = def; - Items[i].Enabled = enabled; - Items[i].Name = name; - Items[i].NewColumn = newcolumn; - Items[i].Radio = radio; - Items[i].SubMenu = sub; - - if (!sub.empty()) { - Items[i].Type = MENU_ITEM_SUBMENU; - } else if (name.empty()) { - Items[i].Type = MENU_ITEM_SEPARATOR; - } else { - Items[i].Type = MENU_ITEM_NORMAL; - } - - Items[i].Icon.Index = 0; - Items[i].Icon.Destroy(); -} - -// ============================================================================= - -void MenuList::Menu::MenuItem::CMenuIcon::Destroy() { - ::DeleteObject(Handle); - Handle = NULL; -} - -void MenuList::Menu::MenuItem::CMenuIcon::Load(HICON hIcon) { - Destroy(); - - RECT rect = {0}; - rect.right = ::GetSystemMetrics(SM_CXMENUCHECK); - rect.bottom = ::GetSystemMetrics(SM_CYMENUCHECK); - - HWND hDesktop = ::GetDesktopWindow(); - HDC hScreen = ::GetDC(hDesktop); - HDC hDC = ::CreateCompatibleDC(hScreen); - Handle = ::CreateCompatibleBitmap(hScreen, rect.right, rect.bottom); - HBITMAP hOld = (HBITMAP)::SelectObject(hDC, Handle); - - ::SetBkColor(hDC, ::GetSysColor(COLOR_MENU)); - ::ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rect, NULL, 0, NULL); - ::DrawIconEx(hDC, 0, 0, hIcon, rect.right, rect.bottom, 0, NULL, DI_NORMAL); - - ::DeleteObject(hOld); - ::DeleteDC(hDC); - ::ReleaseDC(hDesktop, hScreen); - ::DestroyIcon(hIcon); -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_menu.h b/win32/win_menu.h deleted file mode 100644 index f8237bc7f..000000000 --- a/win32/win_menu.h +++ /dev/null @@ -1,82 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef WIN_MENU_H -#define WIN_MENU_H - -#include "win_main.h" - -namespace win32 { - -// ============================================================================= - -enum MenuItemType { - MENU_ITEM_NORMAL = 0, - MENU_ITEM_SEPARATOR, - MENU_ITEM_SUBMENU -}; - -class MenuList { -public: - MenuList() : m_hImageList(NULL) {} - ~MenuList() {} - - void Create(LPCWSTR lpName, LPCWSTR lpType); - HMENU CreateNewMenu(LPCWSTR lpName, vector& hMenu); - int GetIndex(LPCWSTR lpName); - void SetImageList(HIMAGELIST hImageList); - wstring Show(HWND hwnd, int x, int y, LPCWSTR lpName); - - class Menu { - public: - void CreateItem( - wstring action = L"", - wstring name = L"", - wstring sub = L"", - bool checked = false, - bool def = false, - bool enabled = true, - bool newcolumn = false, - bool radio = false); - - class MenuItem { - public: - bool Checked, Default, Enabled, NewColumn, Radio; - wstring Action, Name, SubMenu; - int Type; - - class CMenuIcon { - public: - void Destroy(); - void Load(HICON hIcon); - HBITMAP Handle; - int Index; - } Icon; - }; - vector Items; - wstring Name, Type; - }; - vector Menu; - -private: - HIMAGELIST m_hImageList; -}; - -} // namespace win32 - -#endif // WIN_MENU_H \ No newline at end of file diff --git a/win32/win_registry.cpp b/win32/win_registry.cpp deleted file mode 100644 index 87d3e8237..000000000 --- a/win32/win_registry.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_registry.h" - -namespace win32 { - -// ============================================================================= - -Registry::Registry() : - m_Key(NULL) -{ -} - -Registry::~Registry() { - CloseKey(); -} - -LSTATUS Registry::CloseKey() { - if (m_Key) { - LSTATUS status = ::RegCloseKey(m_Key); - m_Key = NULL; - return status; - } else { - return ERROR_SUCCESS; - } -} - -LSTATUS Registry::CreateKey(HKEY key, const wstring& sub_key, LPWSTR lpClass, DWORD dwOptions, REGSAM samDesired, - LPSECURITY_ATTRIBUTES lpSecurityAttributes, LPDWORD lpdwDisposition) { - CloseKey(); - return ::RegCreateKeyEx(key, sub_key.c_str(), 0, lpClass, dwOptions, samDesired, - lpSecurityAttributes, &m_Key, lpdwDisposition); -} - -LONG Registry::DeleteKey(const wstring& sub_key) { - return DeleteSubkeys(m_Key, sub_key); -} - -LSTATUS Registry::DeleteSubkeys(HKEY root_key, wstring sub_key) { - LSTATUS status = ::RegDeleteKey(root_key, sub_key.c_str()); - if (status == ERROR_SUCCESS) return status; - - HKEY key; - status = ::RegOpenKeyEx(root_key, sub_key.c_str(), 0, KEY_READ, &key); - if (status != ERROR_SUCCESS) return status; - DWORD cSubKeys = 0; - vector keys; - WCHAR name[MAX_PATH] = {0}; - if (::RegQueryInfoKey(key, NULL, NULL, NULL, &cSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) { - for (DWORD i = 0; i < cSubKeys; i++) { - DWORD dwcName = MAX_PATH; - if (::RegEnumKeyEx(key, i, name, &dwcName, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) { - keys.push_back(name); - } - } - } - for (unsigned int i = 0; i < keys.size(); i++) { - DeleteSubkeys(key, keys[i].c_str()); - } - ::RegCloseKey(key); - - return ::RegDeleteKey(root_key, sub_key.c_str()); -} - -LSTATUS Registry::DeleteValue(const wstring& value_name) { - return ::RegDeleteValue(m_Key, value_name.c_str()); -} - -void Registry::EnumKeys(vector& output) { - // samDesired must be: KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS - WCHAR name[MAX_PATH] = {0}; - DWORD cSubKeys = 0; - if (::RegQueryInfoKey(m_Key, NULL, NULL, NULL, &cSubKeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) { - for (DWORD i = 0; i < cSubKeys; i++) { - DWORD dwcName = MAX_PATH; - if (::RegEnumKeyEx(m_Key, i, name, &dwcName, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) { - output.push_back(name); - } - } - } -} - -LSTATUS Registry::OpenKey(HKEY key, const wstring& sub_key, DWORD ulOptions, REGSAM samDesired) { - CloseKey(); - return ::RegOpenKeyEx(key, sub_key.c_str(), ulOptions, samDesired, &m_Key); -} - -LSTATUS Registry::QueryValue(const wstring& value_name, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData) { - return ::RegQueryValueEx(m_Key, value_name.c_str(), NULL, lpType, lpData, lpcbData); -} - -LSTATUS Registry::SetValue(const wstring& value_name, DWORD dwType, CONST BYTE* lpData, DWORD cbData) { - return ::RegSetValueEx(m_Key, value_name.c_str(), 0, dwType, lpData, cbData); -} - -wstring Registry::QueryValue(const wstring& value_name) { - DWORD dwType = 0; - WCHAR szBuffer[MAX_PATH]; - DWORD dwBufferSize = sizeof(szBuffer); - if (QueryValue(value_name.c_str(), &dwType, (LPBYTE)&szBuffer, &dwBufferSize) == ERROR_SUCCESS) { - if (dwType == REG_SZ) return szBuffer; - } - return L""; -} - -void Registry::SetValue(const wstring& value_name, const wstring& value) { - SetValue(value_name.c_str(), REG_SZ, (LPBYTE)value.c_str(), value.length() * sizeof(WCHAR)); -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_registry.h b/win32/win_registry.h deleted file mode 100644 index d4cf3e075..000000000 --- a/win32/win_registry.h +++ /dev/null @@ -1,65 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef WIN_REGISTRY_H -#define WIN_REGISTRY_H - -#include "win_main.h" - -namespace win32 { - -// ============================================================================= - -class Registry { -public: - Registry(); - ~Registry(); - - LSTATUS CloseKey(); - LSTATUS CreateKey(HKEY key, const wstring& sub_key, - LPWSTR lpClass = NULL, - DWORD dwOptions = REG_OPTION_NON_VOLATILE, - REGSAM samDesired = KEY_SET_VALUE, - LPSECURITY_ATTRIBUTES lpSecurityAttributes = NULL, - LPDWORD lpdwDisposition = NULL); - LONG DeleteKey(const wstring& sub_key); - LSTATUS DeleteValue(const wstring& value_name); - void EnumKeys(vector& output); - LSTATUS OpenKey(HKEY key, const wstring& sub_key, - DWORD ulOptions = 0, - REGSAM samDesired = KEY_QUERY_VALUE); - LSTATUS QueryValue(const wstring& value_name, - LPDWORD lpType, - LPBYTE lpData, - LPDWORD lpcbData); - wstring QueryValue(const wstring& value_name); - LSTATUS SetValue(const wstring& value_name, - DWORD dwType, - CONST BYTE* lpData, - DWORD cbData); - void SetValue(const wstring& value_name, const wstring& value); - -private: - LSTATUS DeleteSubkeys(HKEY root_key, wstring sub_key); - - HKEY m_Key; -}; - -} // namespace win32 - -#endif // WIN_REGISTRY_H \ No newline at end of file diff --git a/win32/win_taskbar.cpp b/win32/win_taskbar.cpp deleted file mode 100644 index ed9a5c470..000000000 --- a/win32/win_taskbar.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_main.h" -#include "win_taskbar.h" - -class win32::Taskbar Taskbar; -class win32::TaskbarList TaskbarList; - -static const DWORD WM_TASKBARCALLBACK = WM_APP + 0x15; -static const DWORD WM_TASKBARCREATED = ::RegisterWindowMessage(L"TaskbarCreated"); -static const DWORD WM_TASKBARBUTTONCREATED = ::RegisterWindowMessage(L"TaskbarButtonCreated"); - -namespace win32 { - -#define APP_SYSTRAY_ID 74164 // TAIGA ^_^ - -// ============================================================================= - -Taskbar::Taskbar() : - m_hApp(NULL) -{ - WinVersion win_version = GetWinVersion(); - if (win_version >= VERSION_VISTA) { - m_NID.cbSize = sizeof(NOTIFYICONDATA); - } else if (win_version >= VERSION_XP) { - m_NID.cbSize = NOTIFYICONDATA_V3_SIZE; - } else { - m_NID.cbSize = NOTIFYICONDATA_V2_SIZE; - } -} - -Taskbar::~Taskbar() { - Destroy(); -} - -BOOL Taskbar::Create(HWND hwnd, HICON hIcon, LPCWSTR lpTip) { - Destroy(); - m_hApp = hwnd; - - m_NID.hIcon = hIcon; - m_NID.hWnd = hwnd; - m_NID.szTip[0] = (WCHAR)'\0'; - m_NID.uCallbackMessage = WM_TASKBARCALLBACK; - m_NID.uID = APP_SYSTRAY_ID; - m_NID.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; - - if (hIcon == NULL) { - m_NID.hIcon = reinterpret_cast(LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(101), - IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR)); - } - if (lpTip) { - wcscpy_s(m_NID.szTip, lpTip); - } - - return ::Shell_NotifyIcon(NIM_ADD, &m_NID); -} - -BOOL Taskbar::Destroy() { - if (!m_hApp) return FALSE; - return ::Shell_NotifyIcon(NIM_DELETE, &m_NID); -} - -BOOL Taskbar::Modify(LPCWSTR lpTip) { - if (!m_hApp) return FALSE; - - m_NID.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; - wcsncpy_s(m_NID.szTip, 128, lpTip, _TRUNCATE); - - return ::Shell_NotifyIcon(NIM_MODIFY, &m_NID); -} - -BOOL Taskbar::Tip(LPCWSTR lpText, LPCWSTR lpTitle, int iIconIndex) { - if (!m_hApp) return FALSE; - - m_NID.uFlags = NIF_INFO; - m_NID.dwInfoFlags = iIconIndex; - wcsncpy_s(m_NID.szInfo, 256, lpText, _TRUNCATE); - wcsncpy_s(m_NID.szInfoTitle, 64, lpTitle, _TRUNCATE); - - return ::Shell_NotifyIcon(NIM_MODIFY, &m_NID); -} - -// ============================================================================= - -TaskbarList::TaskbarList() : - m_hWnd(NULL), m_pTaskbarList(NULL) -{ -} - -TaskbarList::~TaskbarList() { - Release(); -} - -void TaskbarList::Initialize(HWND hwnd) { - Release(); - m_hWnd = hwnd; - ::CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, - __uuidof(ITaskbarList3), (void**)&m_pTaskbarList); -} - -void TaskbarList::Release() { - if (m_pTaskbarList) { - m_pTaskbarList->Release(); - m_pTaskbarList = NULL; - m_hWnd = NULL; - } -} - -void TaskbarList::SetProgressState(TBPFLAG flag) { - if (m_pTaskbarList) { - m_pTaskbarList->SetProgressState(m_hWnd, flag); - } -} - -void TaskbarList::SetProgressValue(ULONGLONG ullValue, ULONGLONG ullTotal) { - if (m_pTaskbarList) { - m_pTaskbarList->SetProgressValue(m_hWnd, ullValue, ullTotal); - } -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_taskdialog.cpp b/win32/win_taskdialog.cpp deleted file mode 100644 index 3a0c4efc6..000000000 --- a/win32/win_taskdialog.cpp +++ /dev/null @@ -1,241 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_taskdialog.h" - -namespace win32 { - -vector ButtonText; -LRESULT CALLBACK MsgBoxHookProc(int nCode, WPARAM wParam, LPARAM lParam); - -// ============================================================================= - -TaskDialog::TaskDialog() { - Initialize(); -} - -TaskDialog::TaskDialog(LPCWSTR title, LPWSTR icon) { - Initialize(); - SetWindowTitle(title); - SetMainIcon(icon); -} - -HRESULT CALLBACK TaskDialog::Callback(HWND hwnd, UINT uNotification, WPARAM wParam, LPARAM lParam, LONG_PTR dwRefData) { - switch (uNotification) { - case TDN_DIALOG_CONSTRUCTED: - //::SetForegroundWindow(hwnd); - //::BringWindowToTop(hwnd); - break; - case TDN_HYPERLINK_CLICKED: - ::ShellExecute(NULL, NULL, (LPCWSTR)lParam, NULL, NULL, SW_SHOWNORMAL); - break; - case TDN_VERIFICATION_CLICKED: { - auto dlg = reinterpret_cast(dwRefData); - dlg->m_VerificationChecked = static_cast(wParam) == TRUE; - break; - } - } - return S_OK; -} - -void TaskDialog::Initialize() { - ::ZeroMemory(&m_Config, sizeof(TASKDIALOGCONFIG)); - m_Config.cbSize = sizeof(TASKDIALOGCONFIG); - m_Config.dwFlags = TDF_ALLOW_DIALOG_CANCELLATION | - TDF_ENABLE_HYPERLINKS | - TDF_POSITION_RELATIVE_TO_WINDOW | - TDF_SIZE_TO_CONTENT; - m_Config.hInstance = ::GetModuleHandle(NULL); - m_Config.lpCallbackData = reinterpret_cast(this); - m_Config.pfCallback = Callback; - m_SelectedButtonID = 0; - m_VerificationChecked = false; -} - -// ============================================================================= - -void TaskDialog::AddButton(LPCWSTR text, int id) { - m_Buttons.resize(m_Buttons.size() + 1); - m_Buttons.back().pszButtonText = text; - m_Buttons.back().nButtonID = id; -} - -int TaskDialog::GetSelectedButtonID() const { - return m_SelectedButtonID; -} - -bool TaskDialog::GetVerificationCheck() const { - return m_VerificationChecked; -} - -void TaskDialog::SetCollapsedControlText(LPCWSTR text) { - m_Config.pszCollapsedControlText = text; -} - -void TaskDialog::SetContent(LPCWSTR text) { - m_Config.pszContent = text; -} - -void TaskDialog::SetExpandedControlText(LPCWSTR text) { - m_Config.pszExpandedControlText = text; -} - -void TaskDialog::SetExpandedInformation(LPCWSTR text) { - m_Config.pszExpandedInformation = text; -} - -void TaskDialog::SetFooter(LPCWSTR text) { - m_Config.pszFooter = text; -} - -void TaskDialog::SetFooterIcon(LPWSTR icon) { - m_Config.dwFlags &= ~TDF_USE_HICON_FOOTER; - m_Config.pszFooterIcon = icon; -} - -void TaskDialog::SetMainIcon(LPWSTR icon) { - m_Config.dwFlags &= ~TDF_USE_HICON_MAIN; - m_Config.pszMainIcon = icon; -} - -void TaskDialog::SetMainInstruction(LPCWSTR text) { - m_Config.pszMainInstruction = text; -} - -void TaskDialog::SetVerificationText(LPCWSTR text) { - m_Config.pszVerificationText = text; -} - -void TaskDialog::SetWindowTitle(LPCWSTR text) { - m_Config.pszWindowTitle = text; -} - -void TaskDialog::UseCommandLinks(bool use) { - if (use) { - m_Config.dwFlags |= TDF_USE_COMMAND_LINKS; - } else { - m_Config.dwFlags &= ~TDF_USE_COMMAND_LINKS; - } -} - -// ============================================================================= - -HRESULT TaskDialog::Show(HWND hParent) { - m_Config.hwndParent = hParent; - if (!m_Buttons.empty()) { - m_Config.pButtons = &m_Buttons[0]; - m_Config.cButtons = static_cast(m_Buttons.size()); - if (m_Buttons.size() > 1) { - m_Config.dwFlags &= ~TDF_ALLOW_DIALOG_CANCELLATION; - } - } - - // Show task dialog, if available - if (GetWinVersion() >= VERSION_VISTA) { - BOOL VerificationFlagChecked = TRUE; - return ::TaskDialogIndirect(&m_Config, &m_SelectedButtonID, NULL, &VerificationFlagChecked); - - // Fall back to normal message box - } else { - MSGBOXPARAMS msgbox; - msgbox.cbSize = sizeof(MSGBOXPARAMS); - // Set properties - msgbox.hwndOwner = hParent; - msgbox.hInstance = m_Config.hInstance; - msgbox.lpszCaption = m_Config.pszWindowTitle; - // Set message - #define ADD_MSG(x) if (x) { msg += L"\n\n"; msg += x; } - wstring msg = m_Config.pszMainInstruction; - ADD_MSG(m_Config.pszContent); - ADD_MSG(m_Config.pszExpandedInformation); - ADD_MSG(m_Config.pszFooter); - msgbox.lpszText = msg.c_str(); - // Set buttons - ButtonText.resize(m_Config.cButtons); - for (unsigned int i = 0; i < ButtonText.size(); i++) { - ButtonText[i] = m_Buttons[i].pszButtonText; - unsigned int pos = ButtonText[i].find(L"\n"); - if (pos != wstring::npos) ButtonText[i].resize(pos); - } - switch (m_Config.cButtons) { - case 2: msgbox.dwStyle = MB_YESNO; break; - case 3: msgbox.dwStyle = MB_YESNOCANCEL; break; - default: msgbox.dwStyle = MB_OK; - } - // Set icon - #define icon m_Config.pszMainIcon - if (icon == TD_ICON_INFORMATION || icon == TD_ICON_SHIELD || icon == TD_ICON_SHIELD_GREEN) { - msgbox.dwStyle |= (m_Config.cButtons > 1 ? MB_ICONQUESTION : MB_ICONINFORMATION); - } else if (icon == TD_ICON_WARNING) { - msgbox.dwStyle |= MB_ICONWARNING; - } else if (icon == TD_ICON_ERROR || icon == TD_ICON_SHIELD_RED) { - msgbox.dwStyle |= MB_ICONERROR; - } - #undef icon - // Hook - if (hParent == NULL) hParent = ::GetDesktopWindow(); - ::SetProp(hParent, L"MsgBoxHook", ::SetWindowsHookEx(WH_CALLWNDPROCRET, - MsgBoxHookProc, NULL, ::GetCurrentThreadId())); - // Show message box - m_SelectedButtonID = ::MessageBoxIndirect(&msgbox); - // Unhook - if (::GetProp(hParent, L"MsgBoxHook")) { - ::UnhookWindowsHookEx(reinterpret_cast(::RemoveProp(hParent, L"MsgBoxHook"))); - } - // Return - return S_OK; - } -} - -// MessageBox hook - used to change button text. -// Note that button width is fixed and does not change according to text length. -LRESULT CALLBACK MsgBoxHookProc(int nCode, WPARAM wParam, LPARAM lParam) { - HWND hwnd = reinterpret_cast(lParam)->hwnd; - if (!(nCode < 0)) { - WCHAR szClassName[MAX_PATH]; - GetClassName(hwnd, szClassName, MAX_PATH); - if (!lstrcmpi(szClassName, L"#32770")) { - switch(((LPCWPRETSTRUCT)lParam)->message) { - case WM_INITDIALOG: - #define SETBUTTONTEXT(x, y) \ - SendMessage(GetDlgItem(hwnd, x), WM_SETTEXT, 0, reinterpret_cast(y.c_str())) - switch (ButtonText.size()) { - case 2: - SETBUTTONTEXT(IDYES, ButtonText[0]); - SETBUTTONTEXT(IDNO, ButtonText[1]); - break; - case 3: - SETBUTTONTEXT(IDYES, ButtonText[0]); - SETBUTTONTEXT(IDNO, ButtonText[1]); - SETBUTTONTEXT(IDCANCEL, ButtonText[2]); - break; - default: - SETBUTTONTEXT(IDCANCEL, ButtonText[0]); - } - #undef SETBUTTONTEXT - } - } - } - HWND hParent = GetParent(hwnd); - if (hParent == NULL) hParent = GetDesktopWindow(); - return CallNextHookEx(reinterpret_cast(GetProp(hParent, L"MsgBoxHook")), nCode, wParam, lParam); -} - -// Fallback code is longer than TaskDialog code... -_-' - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_taskdialog.h b/win32/win_taskdialog.h deleted file mode 100644 index 5b934e657..000000000 --- a/win32/win_taskdialog.h +++ /dev/null @@ -1,73 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef WIN_TASKDIALOG_H -#define WIN_TASKDIALOG_H - -#include "win_main.h" - -namespace win32 { - -#define TD_ICON_NONE static_cast(0) -#define TD_ICON_INFORMATION TD_INFORMATION_ICON -#define TD_ICON_WARNING TD_WARNING_ICON -#define TD_ICON_ERROR TD_ERROR_ICON -#define TD_ICON_SHIELD TD_SHIELD_ICON -#define TD_ICON_SHIELD_GREEN MAKEINTRESOURCE(-8) -#define TD_ICON_SHIELD_RED MAKEINTRESOURCE(-7) - -#define TDF_SIZE_TO_CONTENT 0x1000000 - -// ============================================================================= - -class TaskDialog { -public: - TaskDialog(); - TaskDialog(LPCWSTR title, LPWSTR icon); - virtual ~TaskDialog() {} - - void AddButton(LPCWSTR text, int id); - int GetSelectedButtonID() const; - bool GetVerificationCheck() const; - void SetCollapsedControlText(LPCWSTR text); - void SetContent(LPCWSTR text); - void SetExpandedControlText(LPCWSTR text); - void SetExpandedInformation(LPCWSTR text); - void SetFooter(LPCWSTR LPCWSTR); - void SetFooterIcon(LPWSTR icon); - void SetMainIcon(LPWSTR icon); - void SetMainInstruction(LPCWSTR text); - void SetVerificationText(LPCWSTR text); - void SetWindowTitle(LPCWSTR text); - HRESULT Show(HWND hParent); - void UseCommandLinks(bool use); - -protected: - static HRESULT CALLBACK Callback(HWND hwnd, UINT uNotification, - WPARAM wParam, LPARAM lParam, LONG_PTR dwRefData); - void Initialize(); - - vector m_Buttons; - TASKDIALOGCONFIG m_Config; - int m_SelectedButtonID; - bool m_VerificationChecked; -}; - -} // namespace win32 - -#endif // WIN_TASKDIALOG_H \ No newline at end of file diff --git a/win32/win_thread.cpp b/win32/win_thread.cpp deleted file mode 100644 index f729bf889..000000000 --- a/win32/win_thread.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_main.h" -#include "win_thread.h" - -namespace win32 { - -// ============================================================================= - -Thread::Thread() : - m_dwThreadId(0), m_hThread(NULL) -{ -} - -Thread::~Thread() { - CloseThreadHandle(); -} - -bool Thread::CloseThreadHandle() { - BOOL value = TRUE; - if (m_hThread) { - value = ::CloseHandle(m_hThread); - m_hThread = NULL; - } - return value != FALSE; -} - -bool Thread::CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, DWORD dwCreationFlags) { - m_hThread = ::CreateThread(lpThreadAttributes, dwStackSize, ThreadProcStatic, this, dwCreationFlags, &m_dwThreadId); - return m_hThread != NULL; -} - -DWORD WINAPI Thread::ThreadProcStatic(LPVOID lpParam) { - Thread* pThread = reinterpret_cast(lpParam); - return pThread->ThreadProc(); -} - -// ============================================================================= - -CriticalSection::CriticalSection() { - ::InitializeCriticalSectionAndSpinCount(&m_CriticalSection, 0); -} - -CriticalSection::~CriticalSection() { - ::DeleteCriticalSection(&m_CriticalSection); -} - -void CriticalSection::Enter() { - ::EnterCriticalSection(&m_CriticalSection); -} - -void CriticalSection::Leave() { - ::LeaveCriticalSection(&m_CriticalSection); -} - -bool CriticalSection::TryEnter() { - return ::TryEnterCriticalSection(&m_CriticalSection) != FALSE; -} - -void CriticalSection::Wait() { - while (!TryEnter()) { - ::Sleep(1); - } -} - -// ============================================================================= - -Event::Event() : - m_hEvent(NULL) -{ -} - -Event::~Event() { - if (m_hEvent) ::CloseHandle(m_hEvent); -} - -HANDLE Event::Create(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName) { - m_hEvent = ::CreateEvent(lpEventAttributes, bManualReset, bInitialState, lpName); - return m_hEvent; -} - -// ============================================================================= - -Mutex::Mutex() : - m_hMutex(NULL) -{ -} - -Mutex::~Mutex() { - if (m_hMutex) ::CloseHandle(m_hMutex); -} - -HANDLE Mutex::Create(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName) { - m_hMutex = ::CreateMutex(lpMutexAttributes, bInitialOwner, lpName); - return m_hMutex; -} - -HANDLE Mutex::Open(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName) { - m_hMutex = ::OpenMutex(dwDesiredAccess, bInheritHandle, lpName); - return m_hMutex; -} - -bool Mutex::Release() { - BOOL value = TRUE; - if (m_hMutex) { - value = ::ReleaseMutex(m_hMutex); - m_hMutex = NULL; - } - return value != FALSE; -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_thread.h b/win32/win_thread.h deleted file mode 100644 index ed49ee261..000000000 --- a/win32/win_thread.h +++ /dev/null @@ -1,98 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef WIN_THREAD_H -#define WIN_THREAD_H - -#include "win_main.h" - -namespace win32 { - -// ============================================================================= - -class Thread { -public: - Thread(); - virtual ~Thread(); - - virtual DWORD ThreadProc() { return 0; } - - bool CloseThreadHandle(); - bool CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, - SIZE_T dwStackSize, DWORD dwCreationFlags); - - HANDLE GetThreadHandle() const { return m_hThread; } - int GetThreadId() const { return m_dwThreadId; } - -private: - static DWORD WINAPI ThreadProcStatic(LPVOID pCThread); - - HANDLE m_hThread; - DWORD m_dwThreadId; -}; - -// ============================================================================= - -class CriticalSection { -public: - CriticalSection(); - virtual ~CriticalSection(); - - void Enter(); - void Leave(); - bool TryEnter(); - void Wait(); - -private: - CRITICAL_SECTION m_CriticalSection; -}; - -// ============================================================================= - -class Event { -public: - Event(); - virtual ~Event(); - - HANDLE Create(LPSECURITY_ATTRIBUTES lpEventAttributes, - BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName); - -private: - HANDLE m_hEvent; -}; - -// ============================================================================= - -class Mutex { -public: - Mutex(); - virtual ~Mutex(); - - HANDLE Create(LPSECURITY_ATTRIBUTES lpMutexAttributes, - BOOL bInitialOwner, LPCTSTR lpName); - HANDLE Open(DWORD dwDesiredAccess, - BOOL bInheritHandle, LPCTSTR lpName); - bool Release(); - -private: - HANDLE m_hMutex; -}; - -} // namespace win32 - -#endif // WIN_THREAD_H \ No newline at end of file diff --git a/win32/win_window.cpp b/win32/win_window.cpp deleted file mode 100644 index a8f69a3cc..000000000 --- a/win32/win_window.cpp +++ /dev/null @@ -1,653 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "win_main.h" -#include "win_taskbar.h" -#include "win_window.h" - -namespace win32 { - -Window* m_CurrentWindow; - -// ============================================================================= - -Window::Window() : - m_hFont(NULL), m_hIconLarge(NULL), m_hIconSmall(NULL), - m_hMenu(NULL), m_hParent(NULL), m_hWindow(NULL), - m_hInstance(::GetModuleHandle(NULL)) -{ - m_CurrentWindow = NULL; - - ::ZeroMemory(&m_CreateStruct, sizeof(CREATESTRUCT)); - ::ZeroMemory(&m_WndClass, sizeof(WNDCLASSEX)); - - // Create default window class - WNDCLASSEX wc = {0}; - if (!::GetClassInfoEx(m_hInstance, WIN_DEFAULT_CLASS, &wc)) { - wc.cbSize = sizeof(wc); - wc.style = CS_DBLCLKS; - wc.lpfnWndProc = WindowProcStatic; - wc.hInstance = m_hInstance; - wc.hCursor = ::LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH); - wc.lpszClassName = WIN_DEFAULT_CLASS; - ::RegisterClassEx(&wc); - } -} - -Window::Window(HWND hWnd) : - m_hFont(NULL), m_hIconLarge(NULL), m_hIconSmall(NULL), - m_hMenu(NULL), m_hParent(NULL), m_hWindow(NULL), - m_hInstance(::GetModuleHandle(NULL)) -{ - m_CurrentWindow = NULL; - m_hWindow = hWnd; -} - -Window::~Window() { - Destroy(); -} - -// ============================================================================= - -HWND Window::Create(HWND hWndParent) { - PreRegisterClass(m_WndClass); - if (m_WndClass.lpszClassName) { - RegisterClass(m_WndClass); - m_CreateStruct.lpszClass = m_WndClass.lpszClassName; - } - - PreCreate(m_CreateStruct); - if (!m_CreateStruct.lpszClass) { - m_CreateStruct.lpszClass = WIN_DEFAULT_CLASS; - } - if (!hWndParent && m_CreateStruct.hwndParent) { - hWndParent = m_CreateStruct.hwndParent; - } - DWORD dwStyle; - if (m_CreateStruct.style) { - dwStyle = m_CreateStruct.style; - } else { - dwStyle = WS_VISIBLE | (hWndParent ? WS_CHILD : WS_OVERLAPPEDWINDOW); - } - - int x = (m_CreateStruct.cx || m_CreateStruct.cy) ? m_CreateStruct.x : CW_USEDEFAULT; - int cx = (m_CreateStruct.cx || m_CreateStruct.cy) ? m_CreateStruct.cx : CW_USEDEFAULT; - int y = (m_CreateStruct.cx || m_CreateStruct.cy) ? m_CreateStruct.y : CW_USEDEFAULT; - int cy = (m_CreateStruct.cx || m_CreateStruct.cy) ? m_CreateStruct.cy : CW_USEDEFAULT; - - return Create(m_CreateStruct.dwExStyle, - m_CreateStruct.lpszClass, - m_CreateStruct.lpszName, - dwStyle, x, y, cx, cy, hWndParent, - m_CreateStruct.hMenu, - m_CreateStruct.lpCreateParams); -} - -HWND Window::Create(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle, - int X, int Y, int nWidth, int nHeight, - HWND hWndParent, HMENU hMenu, LPVOID lpParam) { - Destroy(); - - m_CurrentWindow = this; - - m_hMenu = hMenu; - m_hParent = hWndParent; - m_hWindow = ::CreateWindowEx(dwExStyle, lpClassName, lpWindowName, dwStyle, - X, Y, nWidth, nHeight, - hWndParent, hMenu, m_hInstance, lpParam); - - WNDCLASSEX wc = {0}; - ::GetClassInfoEx(m_hInstance, lpClassName, &wc); - if (wc.lpfnWndProc != reinterpret_cast(WindowProcStatic)) { - Subclass(m_hWindow); - OnCreate(m_hWindow, &m_CreateStruct); - } - - m_CurrentWindow = NULL; - - return m_hWindow; -} - -void Window::Destroy() { - if (::IsWindow(m_hWindow)) { - ::DestroyWindow(m_hWindow); - } - if (m_hFont && m_hParent) { - ::DeleteObject(m_hFont); - m_hFont = NULL; - } - if (m_hIconLarge) { - ::DestroyIcon(m_hIconLarge); - m_hIconLarge = NULL; - } - if (m_hIconSmall) { - ::DestroyIcon(m_hIconSmall); - m_hIconSmall = NULL; - } - if (m_PrevWindowProc) { - UnSubclass(); - m_PrevWindowProc = NULL; - } - WindowMap.Remove(this); - m_hWindow = NULL; -} - -void Window::PreCreate(CREATESTRUCT& cs) { - m_CreateStruct.cx = cs.cx; - m_CreateStruct.cy = cs.cy; - m_CreateStruct.dwExStyle = cs.dwExStyle; - m_CreateStruct.hInstance = m_hInstance; - m_CreateStruct.hMenu = cs.hMenu; - m_CreateStruct.hwndParent = cs.hwndParent; - m_CreateStruct.lpCreateParams = cs.lpCreateParams; - m_CreateStruct.lpszClass = cs.lpszClass; - m_CreateStruct.lpszName = cs.lpszName; - m_CreateStruct.style = cs.style; - m_CreateStruct.x = cs.x; - m_CreateStruct.y = cs.y; -} - -void Window::PreRegisterClass(WNDCLASSEX& wc) { - m_WndClass.style = wc.style; - m_WndClass.lpfnWndProc = WindowProcStatic; - m_WndClass.cbClsExtra = wc.cbClsExtra; - m_WndClass.cbWndExtra = wc.cbWndExtra; - m_WndClass.hInstance = m_hInstance; - m_WndClass.hIcon = wc.hIcon; - m_WndClass.hCursor = wc.hCursor; - m_WndClass.hbrBackground = wc.hbrBackground; - m_WndClass.lpszMenuName = wc.lpszMenuName; - m_WndClass.lpszClassName = wc.lpszClassName; -} - -BOOL Window::RegisterClass(WNDCLASSEX& wc) { - WNDCLASSEX wc_old = {0}; - if (::GetClassInfoEx(m_hInstance, wc.lpszClassName, &wc_old)) { - wc = wc_old; - return TRUE; - } - - wc.cbSize = sizeof(wc); - wc.hInstance = m_hInstance; - wc.lpfnWndProc = WindowProcStatic; - - return ::RegisterClassEx(&wc); -} - -// ============================================================================= - -void Window::Attach(HWND hWindow) { - Detach(); - if (::IsWindow(hWindow)) { - if (WindowMap.GetWindow(hWindow) == NULL) { - WindowMap.Add(hWindow, this); - Subclass(hWindow); - } - } -} - -void Window::CenterOwner() { - GetParent(); - RECT rcDesktop, rcParent, rcWindow; - ::GetWindowRect(m_hWindow, &rcWindow); - ::SystemParametersInfo(SPI_GETWORKAREA, 0, &rcDesktop, 0); - if (m_hParent != NULL) { - ::GetWindowRect(m_hParent, &rcParent); - } else { - ::CopyRect(&rcParent, &rcDesktop); - } - - HMONITOR hMonitor = MonitorFromWindow(m_hWindow, MONITOR_DEFAULTTONEAREST); - MONITORINFO mi = { sizeof(mi), 0 }; - if (GetMonitorInfo(hMonitor, &mi)) { - rcDesktop = mi.rcWork; - if (m_hParent == NULL) rcParent = mi.rcWork; - } - - ::IntersectRect(&rcParent, &rcParent, &rcDesktop); - ::SetWindowPos(m_hWindow, HWND_TOP, - rcParent.left + ((rcParent.right - rcParent.left) - (rcWindow.right - rcWindow.left)) / 2, - rcParent.top + ((rcParent.bottom - rcParent.top) - (rcWindow.bottom - rcWindow.top)) / 2, - 0, 0, SWP_NOSIZE); -} - -HWND Window::Detach() { - HWND hWnd = m_hWindow; - if (m_PrevWindowProc) UnSubclass(); - WindowMap.Remove(this); - m_hWindow = NULL; - return hWnd; -} - -HICON Window::SetIconLarge(HICON hIcon) { - if (m_hIconLarge) ::DestroyIcon(m_hIconLarge); - m_hIconLarge = hIcon; - if (!m_hIconLarge) return NULL; - return (HICON)::SendMessage(m_hWindow, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)m_hIconLarge); -} - -HICON Window::SetIconLarge(int nIcon) { - return SetIconLarge(reinterpret_cast(LoadImage(m_hInstance, MAKEINTRESOURCE(nIcon), IMAGE_ICON, - ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR))); -} - -HICON Window::SetIconSmall(HICON hIcon) { - if (m_hIconSmall) ::DestroyIcon(m_hIconSmall); - m_hIconSmall = hIcon; - if (!m_hIconSmall) return NULL; - return (HICON)::SendMessage(m_hWindow, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)m_hIconSmall); -} - -HICON Window::SetIconSmall(int nIcon) { - return SetIconSmall(reinterpret_cast(LoadImage(m_hInstance, MAKEINTRESOURCE(nIcon), IMAGE_ICON, - ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR))); -} - -// ============================================================================= - -// Win32 API wrappers - -BOOL Window::BringWindowToTop() const { - return ::BringWindowToTop(m_hWindow); -} - -BOOL Window::Close() const { - return ::CloseWindow(m_hWindow); -} - -BOOL Window::Enable(BOOL bEnable) const { - return ::EnableWindow(m_hWindow, bEnable); -} - -BOOL Window::GetClientRect(LPRECT lpRect) const { - return ::GetClientRect(m_hWindow, lpRect); -} - -HDC Window::GetDC() const { - return ::GetDC(m_hWindow); -} - -HFONT Window::GetFont() { - return reinterpret_cast(::SendMessage(m_hWindow, WM_GETFONT, 0, 0)); -} - -HMENU Window::GetMenu() const { - return ::GetMenu(m_hWindow); -} - -HWND Window::GetParent() { - m_hParent = ::GetParent(m_hWindow); - if (!m_hParent) m_hParent = ::GetDesktopWindow(); - return m_hParent; -} - -void Window::GetText(LPWSTR lpszString, int nMaxCount) const { - ::GetWindowText(m_hWindow, lpszString, nMaxCount); -} - -void Window::GetText(wstring& str) const { - int len = ::GetWindowTextLength(m_hWindow) + 1; - vector buffer(len); - ::GetWindowText(m_hWindow, &buffer[0], len); - str.assign(&buffer[0]); -} - -INT Window::GetTextLength() const { - return ::GetWindowTextLength(m_hWindow); -} - -DWORD Window::GetWindowLong(int nIndex) const { - return ::GetWindowLong(m_hWindow, nIndex); -} - -BOOL Window::GetWindowRect(LPRECT lpRect) const { - return ::GetWindowRect(m_hWindow, lpRect); -} - -void Window::GetWindowRect(HWND hWndTo, LPRECT lpRect) const { - ::GetClientRect(m_hWindow, lpRect); - int width = lpRect->right; - int height = lpRect->bottom; - - ::GetWindowRect(m_hWindow, lpRect); - ::ScreenToClient(hWndTo, reinterpret_cast(lpRect)); - - lpRect->right = lpRect->left + width; - lpRect->bottom = lpRect->top + height; -} - -BOOL Window::Hide() const { - return ::ShowWindow(m_hWindow, SW_HIDE); -} - -BOOL Window::InvalidateRect(LPCRECT lpRect, BOOL bErase) const { - return ::InvalidateRect(m_hWindow, lpRect, bErase); -} - -BOOL Window::IsEnabled() const { - return ::IsWindowEnabled(m_hWindow); -} - -BOOL Window::IsIconic() const { - return ::IsIconic(m_hWindow); -} - -BOOL Window::IsVisible() const { - return ::IsWindowVisible(m_hWindow); -} - -BOOL Window::IsWindow() const { - return ::IsWindow(m_hWindow); -} - -INT Window::MessageBox(LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) const { - return ::MessageBox(m_hWindow, lpText, lpCaption, uType); -} - -LRESULT Window::PostMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { - return ::PostMessage(m_hWindow, uMsg, wParam, lParam); -} - -BOOL Window::RedrawWindow(LPCRECT lprcUpdate, HRGN hrgnUpdate, UINT flags) const { - return ::RedrawWindow(m_hWindow, lprcUpdate, hrgnUpdate, flags); -} - -LRESULT Window::SendMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { - return ::SendMessage(m_hWindow, uMsg, wParam, lParam); -} - -BOOL Window::SetBorder(int iStyle) { - switch (iStyle) { - case WIN_BORDER_NONE: - SetStyle(0, WS_EX_CLIENTEDGE | WS_EX_STATICEDGE, GWL_EXSTYLE); - break; - case WIN_BORDER_CLIENT: - SetStyle(WS_EX_CLIENTEDGE, WS_EX_STATICEDGE, GWL_EXSTYLE); - break; - case WIN_BORDER_STATIC: - SetStyle(WS_EX_STATICEDGE, WS_EX_CLIENTEDGE, GWL_EXSTYLE); - break; - } - return ::SetWindowPos(m_hWindow, NULL, 0, 0, 0, 0, - SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); -} - -HWND Window::SetCapture() const { - return ::SetCapture(m_hWindow); -} - -DWORD Window::SetClassLong(int nIndex, LONG dwNewLong) const { - return ::SetClassLong(m_hWindow, nIndex, dwNewLong); -} - -HWND Window::SetFocus() const { - return ::SetFocus(m_hWindow); -} - -void Window::SetFont(LPCWSTR lpFaceName, int iSize, bool bBold, bool bItalic, bool bUnderline) { - HDC hDC = ::GetDC(m_hWindow); - HFONT hFontOld = reinterpret_cast(::GetCurrentObject(hDC, OBJ_FONT)); - LOGFONT lFont; ::GetObject(hFontOld, sizeof(LOGFONT), &lFont); - - if (lpFaceName) { - ::lstrcpy(lFont.lfFaceName, lpFaceName); - } - if (iSize) { - lFont.lfHeight = -MulDiv(iSize, GetDeviceCaps(hDC, LOGPIXELSY), 72); - lFont.lfWidth = 0; - } - lFont.lfItalic = bItalic; - lFont.lfWeight = bBold ? FW_BOLD : FW_NORMAL; - lFont.lfUnderline = bUnderline; - - if (m_hFont) ::DeleteObject(m_hFont); - m_hFont = ::CreateFontIndirect(&lFont); - ::SendMessage(m_hWindow, WM_SETFONT, reinterpret_cast(m_hFont), TRUE); - - ::DeleteObject(hFontOld); - ::ReleaseDC(m_hWindow, hDC); -} - -void Window::SetFont(HFONT hFont) { - if (m_hFont) ::DeleteObject(m_hFont); m_hFont = hFont; - ::SendMessage(m_hWindow, WM_SETFONT, reinterpret_cast(m_hFont), TRUE); -} - -BOOL Window::SetForegroundWindow() const { - return ::SetForegroundWindow(m_hWindow); -} - -BOOL Window::SetMenu(HMENU hMenu) const { - return ::SetMenu(m_hWindow, hMenu); -} - -void Window::SetParent(HWND hParent) { - if (hParent == NULL) { - ::SetParent(m_hWindow, hParent); - SetStyle(WS_POPUP, WS_CHILD); - } else { - SetStyle(WS_CHILD, WS_POPUP); - ::SetParent(m_hWindow, hParent); - } -} - -BOOL Window::SetPosition(HWND hInsertAfter, int x, int y, int w, int h, UINT uFlags) const { - return ::SetWindowPos(m_hWindow, hInsertAfter, x, y, w, h, uFlags); -} - -BOOL Window::SetPosition(HWND hInsertAfter, const RECT& rc, UINT uFlags) const { - return ::SetWindowPos(m_hWindow, hInsertAfter, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, uFlags); -} - -BOOL Window::SetRedraw(BOOL bRedraw) const { - return ::SendMessage(m_hWindow, WM_SETREDRAW, bRedraw, 0); -} - -void Window::SetStyle(UINT style, UINT style_not, int nIndex) { - UINT old_style = ::GetWindowLong(m_hWindow, nIndex); - ::SetWindowLongPtr(m_hWindow, nIndex, (old_style | style) & ~style_not); -} - -BOOL Window::SetText(LPCWSTR lpszString) const { - return ::SendMessage(m_hWindow, WM_SETTEXT, 0, reinterpret_cast(lpszString)); -} - -BOOL Window::SetText(const std::wstring& str) const { - return SetText(str.c_str()); -} - -HRESULT Window::SetTheme(LPCWSTR pszName) const { - return ::SetWindowTheme(m_hWindow, pszName, NULL); -} - -BOOL Window::SetTransparency(BYTE alpha, COLORREF color) { - if (alpha == 255) { - SetStyle(0, WS_EX_LAYERED, GWL_EXSTYLE); - } else { - SetStyle(WS_EX_LAYERED, 0, GWL_EXSTYLE); - } - - DWORD flags; - if (color & 0xFF000000) { - color = RGB(255, 255, 255); - flags = LWA_ALPHA; - } else { - flags = LWA_COLORKEY; - } - - return ::SetLayeredWindowAttributes(m_hWindow, color, alpha, flags); -} - -BOOL Window::Show(int nCmdShow) const { - return ::ShowWindow(m_hWindow, nCmdShow); -} - -BOOL Window::Update() const { - return ::UpdateWindow(m_hWindow); -} - -// ============================================================================= - -void Window::OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct) { - LOGFONT lFont; - ::GetObject(::GetStockObject(DEFAULT_GUI_FONT), sizeof(lFont), &lFont); - m_hFont = ::CreateFontIndirect(&lFont); - ::SendMessage(m_hWindow, WM_SETFONT, reinterpret_cast(m_hFont), FALSE); -} - -// ============================================================================= - -void Window::Subclass(HWND hWnd) { - WNDPROC current_proc = reinterpret_cast(::GetWindowLongPtr(hWnd, GWLP_WNDPROC)); - if (current_proc != reinterpret_cast(WindowProcStatic)) { - m_PrevWindowProc = reinterpret_cast(::SetWindowLongPtr(hWnd, GWLP_WNDPROC, - reinterpret_cast(WindowProcStatic))); - m_hWindow = hWnd; - } -} - -void Window::UnSubclass() { - WNDPROC current_proc = reinterpret_cast(::GetWindowLongPtr(m_hWindow, GWLP_WNDPROC)); - if (current_proc == reinterpret_cast(WindowProcStatic)) { - ::SetWindowLongPtr(m_hWindow, GWLP_WNDPROC, reinterpret_cast(m_PrevWindowProc)); - m_PrevWindowProc = NULL; - } -} - -LRESULT CALLBACK Window::WindowProcStatic(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - Window* window = WindowMap.GetWindow(hwnd); - if (!window) { - window = m_CurrentWindow; - if (window) { - window->SetWindowHandle(hwnd); - WindowMap.Add(hwnd, window); - } - } - if (window) { - return window->WindowProc(hwnd, uMsg, wParam, lParam); - } else { - return ::DefWindowProc(hwnd, uMsg, wParam, lParam); - } -} - -LRESULT Window::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - return WindowProcDefault(hwnd, uMsg, wParam, lParam); -} - -LRESULT Window::WindowProcDefault(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_COMMAND: { - if (OnCommand(wParam, lParam)) return 0; - break; - } - case WM_CREATE: { - OnCreate(hwnd, reinterpret_cast(lParam)); - break; - } - case WM_DESTROY: { - if (OnDestroy()) return 0; - break; - } - case WM_DROPFILES: { - OnDropFiles(reinterpret_cast(wParam)); - break; - } - case WM_ENTERSIZEMOVE: - case WM_EXITSIZEMOVE: { - SIZE size = {0}; - OnSize(uMsg, 0, size); - break; - } - case WM_GETMINMAXINFO: { - OnGetMinMaxInfo(reinterpret_cast(lParam)); - break; - } - case WM_LBUTTONDOWN: - case WM_MBUTTONDOWN: - case WM_RBUTTONDOWN: - case WM_LBUTTONUP: - case WM_MBUTTONUP: - case WM_RBUTTONUP: - case WM_MOUSEACTIVATE: - case WM_MOUSEHOVER: - case WM_MOUSEHWHEEL: - case WM_MOUSELEAVE: - case WM_MOUSEMOVE: - case WM_MOUSEWHEEL: { - LRESULT lResult = OnMouseEvent(uMsg, wParam, lParam); - if (lResult != -1) return lResult; - break; - } - case WM_MOVE: { - POINTS pts = MAKEPOINTS(lParam); - OnMove(&pts); - break; - } - case WM_NOTIFY: { - LRESULT lResult = OnNotify(static_cast(wParam), reinterpret_cast(lParam)); - if (lResult) return lResult; - break; - } - case WM_PAINT: { - if (!m_PrevWindowProc) { - if (::GetUpdateRect(hwnd, NULL, FALSE)) { - PAINTSTRUCT ps; - HDC hdc = ::BeginPaint(hwnd, &ps); - OnPaint(hdc, &ps); - ::EndPaint(hwnd, &ps); - } else { - HDC hdc = ::GetDC(hwnd); - OnPaint(hdc, NULL); - ::ReleaseDC(hwnd, hdc); - } - } - break; - } - case WM_SIZE: { - SIZE size = {LOWORD(lParam), HIWORD(lParam)}; - OnSize(uMsg, static_cast(wParam), size); - break; - } - case WM_TIMER: { - OnTimer(static_cast(wParam)); - break; - } - case WM_WINDOWPOSCHANGING: { - OnWindowPosChanging(reinterpret_cast(lParam)); - break; - } - default: { - if (uMsg == WM_TASKBARCREATED || - uMsg == WM_TASKBARBUTTONCREATED || - uMsg == WM_TASKBARCALLBACK) { - OnTaskbarCallback(uMsg, lParam); - return 0; - } - break; - } - } - - if (m_PrevWindowProc) { - return ::CallWindowProc(m_PrevWindowProc, hwnd, uMsg, wParam, lParam); - } else { - return ::DefWindowProc(hwnd, uMsg, wParam, lParam); - } -} - -} // namespace win32 \ No newline at end of file diff --git a/win32/win_window.h b/win32/win_window.h deleted file mode 100644 index e580f5c31..000000000 --- a/win32/win_window.h +++ /dev/null @@ -1,155 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef WIN_WINDOW_H -#define WIN_WINDOW_H - -#include "win_main.h" - -namespace win32 { - -#define WIN_CONTROL_MARGIN 6 -#define WIN_DEFAULT_CLASS L"TaigaDefaultW" - -enum WinBorderStyle { - WIN_BORDER_NONE, - WIN_BORDER_CLIENT, - WIN_BORDER_STATIC -}; - -// ============================================================================= - -/* Window class */ - -class Window { -public: - Window(); - Window(HWND hWnd); - virtual ~Window(); - - virtual HWND Create(HWND hWndParent = NULL); - virtual HWND Create(DWORD dwExStyle, - LPCWSTR lpClassName, - LPCWSTR lpWindowName, - DWORD dwStyle, - int X, int Y, - int nWidth, int nHeight, - HWND hWndParent, - HMENU hMenu, - LPVOID lpParam); - virtual void Destroy(); - virtual void PreCreate(CREATESTRUCT& cs); - virtual void PreRegisterClass(WNDCLASSEX& wc); - virtual BOOL PreTranslateMessage(MSG* pMsg) { return FALSE; }; - - void Attach(HWND hWindow); - void CenterOwner(); - HWND Detach(); - LPCWSTR GetClassName() const { return m_WndClass.lpszClassName; }; - HMENU GetMenuHandle() const { return m_hMenu; }; - HWND GetParentHandle() const { return m_hParent; }; - HICON SetIconLarge(HICON hIcon); - HICON SetIconLarge(int nIcon); - HICON SetIconSmall(HICON hIcon); - HICON SetIconSmall(int nIcon); - HWND GetWindowHandle() const { return m_hWindow; }; - void SetWindowHandle(HWND hWnd) { m_hWindow = hWnd; }; - - // Win32 API wrappers - BOOL BringWindowToTop() const; - BOOL Close() const; - BOOL Enable(BOOL bEnable = TRUE) const; - BOOL GetClientRect(LPRECT lpRect) const; - HDC GetDC() const; - HFONT GetFont(); - HMENU GetMenu() const; - HWND GetParent(); - void GetText(LPWSTR lpszString, int nMaxCount = MAX_PATH) const; - void GetText(std::wstring& str) const; - INT GetTextLength() const; - DWORD GetWindowLong(int nIndex = GWL_STYLE) const; - BOOL GetWindowRect(LPRECT lpRect) const; - void GetWindowRect(HWND hWndTo, LPRECT lpRect) const; - BOOL Hide() const; - BOOL InvalidateRect(LPCRECT lpRect = NULL, BOOL bErase = TRUE) const; - BOOL IsEnabled() const; - BOOL IsIconic() const; - BOOL IsVisible() const; - BOOL IsWindow() const; - INT MessageBox(LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) const; - LRESULT PostMessage(UINT uMsg, WPARAM wParam = 0, LPARAM lParam = 0); - BOOL RedrawWindow(LPCRECT lprcUpdate = NULL, HRGN hrgnUpdate = NULL, UINT flags = RDW_INVALIDATE | RDW_ALLCHILDREN) const; - LRESULT SendMessage(UINT uMsg, WPARAM wParam = 0, LPARAM lParam = 0); - BOOL SetBorder(int iStyle); - HWND SetCapture() const; - DWORD SetClassLong(int nIndex, LONG dwNewLong) const; - HWND SetFocus() const; - void SetFont(LPCWSTR lpFaceName, int iSize, bool bBold = false, bool bItalic = false, bool bUnderline = false); - void SetFont(HFONT hFont); - BOOL SetForegroundWindow() const; - BOOL SetMenu(HMENU hMenu) const; - void SetParent(HWND hParent); - BOOL SetPosition(HWND hInsertAfter, int x, int y, int w, int h, UINT uFlags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER) const; - BOOL SetPosition(HWND hInsertAfter, const RECT& rc, UINT uFlags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER) const; - BOOL SetRedraw(BOOL bRedraw) const; - void SetStyle(UINT style, UINT style_not, int nIndex = GWL_STYLE); - BOOL SetText(LPCWSTR lpszString) const; - BOOL SetText(const std::wstring& str) const; - HRESULT SetTheme(LPCWSTR pszName = L"explorer") const; - BOOL SetTransparency(BYTE alpha, COLORREF color = 0xFF000000); - BOOL Show(int nCmdShow = SW_SHOWNORMAL) const; - BOOL Update() const; - -protected: - // Message handlers - virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam) { return FALSE; }; - virtual void OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct); - virtual BOOL OnDestroy() { return FALSE; }; - virtual void OnDropFiles(HDROP hDropInfo) {}; - virtual void OnGetMinMaxInfo(LPMINMAXINFO lpMMI) {}; - virtual LRESULT OnMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) { return -1; }; - virtual void OnMove(LPPOINTS ptsPos) {}; - virtual LRESULT OnNotify(int idCtrl, LPNMHDR pnmh) { return 0; }; - virtual void OnPaint(HDC hdc, LPPAINTSTRUCT lpps) {}; - virtual void OnSize(UINT uMsg, UINT nType, SIZE size) {}; - virtual void OnTaskbarCallback(UINT uMsg, LPARAM lParam) {}; - virtual void OnTimer(UINT_PTR nIDEvent) {}; - virtual void OnWindowPosChanging(LPWINDOWPOS lpWndPos) {}; - virtual LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - virtual LRESULT WindowProcDefault(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - - CREATESTRUCT m_CreateStruct; - HFONT m_hFont; - HICON m_hIconLarge, m_hIconSmall; - HINSTANCE m_hInstance; - HMENU m_hMenu; - HWND m_hParent, m_hWindow; - WNDCLASSEX m_WndClass; - WNDPROC m_PrevWindowProc; - -private: - static LRESULT CALLBACK WindowProcStatic(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - - BOOL RegisterClass(WNDCLASSEX& wc); - void Subclass(HWND hWnd); - void UnSubclass(); -}; - -} // namespace win32 - -#endif // WIN_WINDOW_H \ No newline at end of file diff --git a/xml.cpp b/xml.cpp deleted file mode 100644 index bf59a8ae0..000000000 --- a/xml.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#include "std.h" - -#include "xml.h" - -#include "string.h" - -// ============================================================================= - -int XML_ReadIntValue(xml_node& node, const wchar_t* name) { - return _wtoi(node.child_value(name)); -} - -wstring XML_ReadStrValue(xml_node& node, const wchar_t* name) { - return node.child_value(name); -} - -void XML_ReadChildNodes(xml_node& parent_node, vector& str_vector, const wchar_t* name) { - for (xml_node child_node = parent_node.child(name); child_node; child_node = child_node.next_sibling(name)) { - str_vector.push_back(child_node.child_value()); - } -} - -void XML_WriteChildNodes(xml_node& parent_node, vector& str_vector, const wchar_t* name) { - for (size_t i = 0; i < str_vector.size(); i++) { - xml_node child_node = parent_node.append_child(name); - child_node.append_child(node_pcdata).set_value(str_vector[i].c_str()); - } -} - -void XML_WriteIntValue(xml_node& node, const wchar_t* name, int value) { - xml_node child = node.append_child(name); - child.append_child(node_pcdata).set_value(ToWstr(value).c_str()); -} - -void XML_WriteStrValue(xml_node& node, const wchar_t* name, const wchar_t* value, xml_node_type node_type) { - xml_node child = node.append_child(name); - child.append_child(node_type).set_value(value); -} \ No newline at end of file diff --git a/xml.h b/xml.h deleted file mode 100644 index dc7b678ff..000000000 --- a/xml.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -** Taiga, a lightweight client for MyAnimeList -** Copyright (C) 2010-2012, Eren Okka -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -#ifndef XML_H -#define XML_H - -#include "std.h" -#include "third_party\pugixml\pugixml.hpp" - -using namespace pugi; - -// ============================================================================= - -int XML_ReadIntValue(xml_node& node, const wchar_t* name); -wstring XML_ReadStrValue(xml_node& node, const wchar_t* name); -void XML_ReadChildNodes(xml_node& parent_node, vector& str_vector, const wchar_t* name); -void XML_WriteChildNodes(xml_node& parent_node, vector& str_vector, const wchar_t* name); -void XML_WriteIntValue(xml_node& node, const wchar_t* name, int value); -void XML_WriteStrValue(xml_node& node, const wchar_t* name, const wchar_t* value, xml_node_type node_type = node_pcdata); - -#endif // XML_H \ No newline at end of file