summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesusaves <cpntb1@ymail.com>2022-02-22 07:09:31 -0300
committerJesusaves <cpntb1@ymail.com>2022-02-22 07:09:31 -0300
commitb9d57b7ee57e79820c97c4d452c07972b73f3c7c (patch)
tree06a98c67240ee509b19d26ee54d8515f06f37230
parenta0c410d5ac8eeeeb4d7295c8f54931fa32664f4d (diff)
downloadtools-b9d57b7ee57e79820c97c4d452c07972b73f3c7c.tar.gz
tools-b9d57b7ee57e79820c97c4d452c07972b73f3c7c.tar.bz2
tools-b9d57b7ee57e79820c97c4d452c07972b73f3c7c.tar.xz
tools-b9d57b7ee57e79820c97c4d452c07972b73f3c7c.zip
Include saedit2
-rw-r--r--saedit/.gitignore6
-rw-r--r--saedit/.gitlab-ci.yml6
-rw-r--r--saedit/LICENSE674
-rw-r--r--saedit/Makefile33
-rw-r--r--saedit/README.md7
-rw-r--r--saedit/action.c176
-rw-r--r--saedit/action.h59
-rw-r--r--saedit/animation.c386
-rw-r--r--saedit/animation.h59
-rw-r--r--saedit/buffer.c87
-rw-r--r--saedit/buffer.h22
-rw-r--r--saedit/callbacks.c432
-rw-r--r--saedit/callbacks.h96
-rw-r--r--saedit/common.c22
-rw-r--r--saedit/common.h12
-rw-r--r--saedit/config.c191
-rw-r--r--saedit/config.h32
-rw-r--r--saedit/context.c227
-rw-r--r--saedit/context.h50
-rw-r--r--saedit/drawfuncs.c107
-rw-r--r--saedit/drawfuncs.h33
-rw-r--r--saedit/errors.c23
-rw-r--r--saedit/errors.h12
-rw-r--r--saedit/file.c103
-rw-r--r--saedit/file.h16
-rwxr-xr-xsaedit/glade.sh3
-rw-r--r--saedit/glade/saedit-catalog.xml26
-rw-r--r--saedit/iface.ui577
-rw-r--r--saedit/imageset.c130
-rw-r--r--saedit/imageset.h50
-rw-r--r--saedit/interactor.c439
-rw-r--r--saedit/interactor.h110
-rw-r--r--saedit/logo.svg657
-rw-r--r--saedit/main.c235
-rw-r--r--saedit/main.h51
-rw-r--r--saedit/spritedrawingarea/sdalayer.c271
-rw-r--r--saedit/spritedrawingarea/sdalayer.h54
-rw-r--r--saedit/spritedrawingarea/sdalayerprivate.h19
-rw-r--r--saedit/spritedrawingarea/spritedrawingarea.c437
-rw-r--r--saedit/spritedrawingarea/spritedrawingarea.h71
-rw-r--r--saedit/treefolderview/treefolderview.c400
-rw-r--r--saedit/treefolderview/treefolderview.h64
-rw-r--r--saedit/treefolderview/treefolderviewprivate.h45
-rw-r--r--saedit/treefolderview/type.c250
-rw-r--r--saedit/xml.c385
-rw-r--r--saedit/xml.h70
-rw-r--r--saedit/xmlsetup.c165
-rw-r--r--saedit/xmlsetup.h10
48 files changed, 7390 insertions, 0 deletions
diff --git a/saedit/.gitignore b/saedit/.gitignore
new file mode 100644
index 0000000..e680e74
--- /dev/null
+++ b/saedit/.gitignore
@@ -0,0 +1,6 @@
+*~
+*.swp
+*.o
+*/*.o
+glade/libsaedit.so
+saedit
diff --git a/saedit/.gitlab-ci.yml b/saedit/.gitlab-ci.yml
new file mode 100644
index 0000000..0807941
--- /dev/null
+++ b/saedit/.gitlab-ci.yml
@@ -0,0 +1,6 @@
+before_script:
+ - apt-get update -qq && apt-get install -y -qq gtk+3.0 gtksourceview-3.0
+
+saedit:
+ script:
+ - make all
diff --git a/saedit/LICENSE b/saedit/LICENSE
new file mode 100644
index 0000000..eb82ba2
--- /dev/null
+++ b/saedit/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ 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.
+
+ saedit2
+ Copyright (C) 2018 Danil Sagunov
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General 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/>.
+
+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:
+
+ saedit2 Copyright (C) 2018 Danil Sagunov
+ 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
+<http://www.gnu.org/licenses/>.
+
+ 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
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/saedit/Makefile b/saedit/Makefile
new file mode 100644
index 0000000..207eeaf
--- /dev/null
+++ b/saedit/Makefile
@@ -0,0 +1,33 @@
+CC ?= gcc
+
+CFLAGS += `pkg-config --cflags gtk+-3.0 gtksourceview-3.0`
+CFLAGS += -fPIC -Itreefolderview -Ispritedrawingarea
+CFLAGS += -Wall -Wdeclaration-after-statement -ansi
+CFLAGS += -Werror -Wextra -Wstrict-prototypes
+CFLAGS += -Wno-unused-parameter
+
+LDFLAGS += `pkg-config --libs gtk+-3.0 gtksourceview-3.0`
+LDFLAGS += -rdynamic -Ltreefolderview -Lspritedrawingarea
+
+FLAGS = ${CFLAGS} ${LDFLAGS}
+
+all: saedit glade/libsaedit.so
+
+saedit: main.o treefolderview/treefolderview.o xml.o \
+ spritedrawingarea/spritedrawingarea.o \
+ context.o imageset.o action.o animation.o common.o \
+ interactor.o callbacks.o errors.o config.o file.o \
+ buffer.o spritedrawingarea/sdalayer.o xmlsetup.o \
+ drawfuncs.o
+ ${CC} $^ -o saedit ${FLAGS}
+
+glade/libsaedit.so: treefolderview/treefolderview.o \
+ spritedrawingarea/spritedrawingarea.o \
+ spritedrawingarea/sdalayer.o
+ ${CC} $^ -o glade/libsaedit.so ${FLAGS} -shared
+
+%.o: %.c
+ ${CC} $^ -c -o $@ ${CFLAGS}
+
+clean:
+ rm -f *.o */*.o *~ glade/libsaedit.so saedit
diff --git a/saedit/README.md b/saedit/README.md
new file mode 100644
index 0000000..4be496d
--- /dev/null
+++ b/saedit/README.md
@@ -0,0 +1,7 @@
+# Instructions
+
+1. Run make
+2. Fix dependencies until if exits successfully
+3. Run ./saedit binary, which will have been created if it ran successfully.
+
+Bugs report to Vasily.
diff --git a/saedit/action.c b/saedit/action.c
new file mode 100644
index 0000000..a9b31e2
--- /dev/null
+++ b/saedit/action.c
@@ -0,0 +1,176 @@
+#include "action.h"
+#include "animation.h"
+#include "imageset.h"
+#include "context.h"
+
+Action *
+action_new (
+ const SpriteContext *context,
+ const XMLNode *node,
+ gint included_from
+) {
+ Action *action;
+ GList *list;
+ gchar *imgset_name;
+ gboolean fail = FALSE;
+ Imageset *imageset;
+
+ g_return_val_if_fail (g_strcmp0 (node->name, "action") == 0, NULL);
+
+ action = (Action *) g_new0 (Action, 1);
+
+ action->name = xml_node_get_attr_value (node, "name");
+ if (action->name == NULL) {
+ /* TODO: report error */
+ fail = TRUE;
+ }
+
+ imgset_name = xml_node_get_attr_value (node, "imageset");
+ if (imgset_name == NULL) {
+ /* TODO: report error */
+ fail = TRUE;
+ } else {
+ imageset = sprite_context_get_imageset (context, imgset_name);
+ if (imageset == NULL) {
+ /* TODO: report error */
+ fail = TRUE;
+ }
+ }
+
+ if (fail) {
+ g_free (action);
+ return NULL;
+ }
+
+ action->hp = xml_node_get_int_attr_value (node, "hp", 100);
+
+ list = node->sub_nodes;
+ while (list != NULL) {
+ XMLNode *current = (XMLNode *) list->data;
+
+ if (g_strcmp0 (current->name, "animation") == 0) {
+ action_add_animation (
+ action,
+ imageset,
+ current,
+ included_from
+ );
+ } else {
+ /* TODO: action contains something unknown */
+ }
+
+ list = g_list_next (list);
+ }
+
+ return action;
+}
+
+void
+action_add_animation (
+ Action *action,
+ const Imageset *imageset,
+ const XMLNode *node,
+ gint included_from
+) {
+ Animation *animation;
+
+ g_return_if_fail (g_strcmp0 (node->name, "animation") == 0);
+
+ animation = animation_new (imageset, node, included_from);
+ if (animation == NULL)
+ return;
+
+ if (g_list_find_custom (
+ action->animations, animation,
+ (GCompareFunc) animation_compare_by_direction) != NULL
+ ) {
+ animation_free (animation);
+ return;
+ }
+
+ action->animations = g_list_append (action->animations, animation);
+}
+
+void
+action_free (Action *action) {
+ g_list_free_full (
+ action->animations,
+ (GDestroyNotify) animation_free
+ );
+
+ g_free (action->name);
+ g_free (action);
+}
+
+gint
+action_compare_by_hp_and_name (
+ const Action *first,
+ const Action *second
+) {
+ if (first->hp != second->hp) {
+ return first->hp - second->hp;
+ }
+ return g_strcmp0 (first->name, second->name);
+}
+
+gboolean
+action_hp_and_name_equals (
+ const Action *action,
+ gint hp,
+ const gchar *name
+) {
+ return
+ action->hp == hp &&
+ g_strcmp0 (action->name, name) == 0;
+}
+
+const Animation *
+action_get_animation (
+ const Action *action,
+ const gchar *direction
+) {
+ GList *animation = action->animations;
+ g_return_val_if_fail (animation != NULL, NULL);
+
+ while (direction != NULL && animation != NULL) {
+ Animation *current = (Animation *) animation->data;
+ g_assert (current != NULL);
+ if (animation_direction_equals (current, direction))
+ return current;
+
+ animation = g_list_next (animation);
+ }
+
+ return (Animation *) action->animations->data;
+}
+
+void
+action_get_hp_and_name (
+ const Action *action,
+ gint *hp,
+ gchar **name
+) {
+ if (hp != NULL)
+ *hp = action->hp;
+ if (name != NULL)
+ *name = action->name;
+}
+
+GList *
+action_get_directions (const Action *action) {
+ GList *result = NULL;
+ GList *animations = action->animations;
+
+ while (animations != NULL) {
+ Animation *animation = (Animation *) animations->data;
+ result = g_list_append (result, animation->direction);
+ animations = g_list_next (animations);
+ }
+
+ return result;
+}
+
+gchar *
+get_action_id (gint hp, const gchar *name) {
+ return g_strdup_printf ("%d:%s", hp, name);
+}
diff --git a/saedit/action.h b/saedit/action.h
new file mode 100644
index 0000000..57da155
--- /dev/null
+++ b/saedit/action.h
@@ -0,0 +1,59 @@
+#ifndef ACTION_H
+#define ACTION_H
+
+#include <glib.h>
+#include "common.h"
+#include "imageset.h"
+#include "animation.h"
+#include "xml.h"
+
+typedef struct {
+ gchar *name;
+ gint hp;
+ GList *animations;
+} Action;
+
+void
+action_free (Action *action);
+
+gint
+action_compare_by_hp_and_name (
+ const Action *first,
+ const Action *second
+);
+
+gboolean
+action_hp_and_name_equals (
+ const Action *action,
+ gint hp,
+ const gchar *name
+);
+
+void
+action_add_animation (
+ Action *action,
+ const Imageset *imageset,
+ const XMLNode *node,
+ gint included_from
+);
+
+const Animation *
+action_get_animation (
+ const Action *action,
+ const gchar *direction
+);
+
+void
+action_get_hp_and_name (
+ const Action *action,
+ gint *hp,
+ gchar **name
+);
+
+GList *
+action_get_directions (const Action *action);
+
+gchar *
+get_action_id (gint hp, const gchar *name);
+
+#endif
diff --git a/saedit/animation.c b/saedit/animation.c
new file mode 100644
index 0000000..997b420
--- /dev/null
+++ b/saedit/animation.c
@@ -0,0 +1,386 @@
+#include "animation.h"
+#include <string.h>
+
+static void
+animation_add_element_pause (
+ Animation *animation,
+ gint delay,
+ gint rand,
+ gint line_no
+) {
+ AnimElement *elem = animation_element_new ();
+ elem->delay = delay;
+ elem->rand = rand;
+ elem->type = ELEMENT_PAUSE;
+ elem->line_no = line_no;
+
+ animation->elements = g_list_append (animation->elements, elem);
+}
+
+static void
+animation_add_element_end (
+ Animation *animation,
+ gint rand,
+ gint line_no
+) {
+ AnimElement *elem = animation_element_new ();
+ elem->type = ELEMENT_END;
+ elem->rand = rand;
+ elem->line_no = line_no;
+
+ animation->elements = g_list_append (animation->elements, elem);
+}
+
+static void
+animation_add_element_frame (
+ Animation *animation,
+ const Imageset *imageset,
+ gint index,
+ gint offsetX,
+ gint offsetY,
+ gint delay,
+ gint rand,
+ gint line_no
+) {
+ gint x, y;
+ AnimElement *elem = animation_element_new ();
+
+ elem->type = ELEMENT_FRAME;
+
+ imageset_get_offset (imageset, &x, &y);
+ offsetX += x;
+ offsetY += y;
+
+ imageset_get_size (imageset, &x, &y);
+ offsetX -= x / 2;
+ offsetY -= y;
+
+ elem->offsetX = offsetX;
+ elem->offsetY = offsetY;
+
+ elem->delay = delay;
+ elem->rand = rand;
+ elem->line_no = line_no;
+
+ elem->sprite = imageset_get_sprite_by_index (imageset, index);
+ if (elem->sprite == NULL) {
+ /* TODO: report this */
+ g_free (elem);
+ return;
+ }
+
+ animation->elements = g_list_append (animation->elements, elem);
+}
+
+static void
+animation_add_element_goto (
+ Animation *animation,
+ gchar *label,
+ gint rand,
+ gint line_no
+) {
+ AnimElement *elem = animation_element_new ();
+ elem->type = ELEMENT_GOTO;
+ elem->str = label;
+ elem->rand = rand;
+ elem->line_no = line_no;
+
+ animation->elements = g_list_append (animation->elements, elem);
+}
+
+static void
+animation_add_element_jump (
+ Animation *animation,
+ gchar *action,
+ gint rand,
+ gint line_no
+) {
+ AnimElement *elem = animation_element_new ();
+ elem->type = ELEMENT_JUMP;
+ elem->str = action;
+ elem->rand = rand;
+ elem->line_no = line_no;
+
+ animation->elements = g_list_append (animation->elements, elem);
+}
+
+static void
+animation_add_element_label (
+ Animation *animation,
+ gchar *name,
+ gint line_no
+) {
+ AnimElement *elem = animation_element_new ();
+ elem->type = ELEMENT_LABEL;
+ elem->str = name;
+ elem->line_no = line_no;
+
+ animation->elements = g_list_append (animation->elements, elem);
+}
+
+static void
+animation_add_sequence (
+ Animation *animation,
+ const Imageset *imageset,
+ const XMLNode *node,
+ gint offsetX,
+ gint offsetY,
+ gint delay,
+ gint rand,
+ gint line_no
+) {
+ gchar *value, **tokens;
+ gint start = xml_node_get_int_attr_value (node, "start", -1);
+ gint end = xml_node_get_int_attr_value (node, "end", -1);
+ gint repeat = xml_node_get_int_attr_value_limited (
+ node, "repeat", 1, 0, 100
+ );
+
+ g_return_if_fail (g_strcmp0 (node->name, "sequence") == 0);
+
+ if (repeat < 1) {
+ /* TODO: show error */
+ return;
+ }
+
+ value = xml_node_get_attr_value (node, "value");
+
+ if (value == NULL) {
+ if (start < 0 || end < 0) {
+ /* TODO: show error */
+ return;
+ }
+ value = g_strdup_printf ("%d-%d", start, end);
+ }
+
+ tokens = g_strsplit (value, ",", 0);
+ g_free (value);
+
+ while (repeat > 0) {
+ gchar **token = tokens;
+
+ while (*token != NULL) {
+ if (g_strcmp0 (*token, "p") == 0) {
+ animation_add_element_pause (
+ animation,
+ delay,
+ rand,
+ line_no
+ );
+ } else {
+ gint start = -1, end = -1, d;
+ gchar *delim = strchr (*token, '-');
+
+ if (delim == NULL) {
+ try_strtoint (*token, &start);
+ end = start;
+ } else {
+ *delim = 0;
+ try_strtoint ( *token, &start);
+ try_strtoint (delim + 1, &end);
+ *delim = '-';
+ }
+
+ if (start < 0 || end < 0) {
+ /* TODO: show error */
+ g_strfreev (tokens);
+ return;
+ }
+
+ d = start <= end ? +1 : -1;
+ end += d;
+
+ while (start != end) {
+ animation_add_element_frame (
+ animation,
+ imageset,
+ start,
+ offsetX,
+ offsetY,
+ delay,
+ rand,
+ line_no
+ );
+ start += d;
+ }
+ }
+ ++token;
+ }
+ --repeat;
+ }
+
+ g_strfreev (tokens);
+}
+
+static void
+animation_add_elements_from_node (
+ Animation *animation,
+ const Imageset *imageset,
+ const XMLNode *node,
+ gint included_from
+) {
+ gchar *name = node->name;
+
+ gint offsetX, offsetY;
+ gint delay, rand;
+ gchar *str;
+ gint line_no;
+
+ imageset_get_offset (imageset, &offsetX, &offsetY);
+
+ offsetX += xml_node_get_int_attr_value (node, "offsetX", 0);
+ offsetY += xml_node_get_int_attr_value (node, "offsetY", 0);
+
+ delay = xml_node_get_int_attr_value_limited (
+ node, "delay", 0,
+ 0, 100000
+ );
+
+ rand = xml_node_get_int_attr_value_limited (
+ node, "rand", 100,
+ 0, 100
+ );
+
+ line_no = included_from;
+ if (line_no == -1)
+ line_no = node->line_no;
+
+ if (g_strcmp0 (name, "frame") == 0) {
+ gint index = xml_node_get_int_attr_value (node, "index", -1);
+
+ if (index < 0) {
+ /* TODO: report error */
+ return;
+ }
+
+ animation_add_element_frame (
+ animation,
+ imageset,
+ index,
+ offsetX, offsetY,
+ delay, rand,
+ line_no
+ );
+ } else
+
+ if (g_strcmp0 (name, "pause") == 0) {
+ animation_add_element_pause (
+ animation, delay, rand, line_no
+ );
+ } else
+
+ if (g_strcmp0 (name, "end") == 0) {
+ animation_add_element_end (animation, rand, line_no);
+ } else
+
+ if (g_strcmp0 (name, "jump") == 0) {
+ str = xml_node_get_attr_value (node, "action");
+ animation_add_element_jump (
+ animation, str, rand, line_no
+ );
+ } else
+
+ if (g_strcmp0 (name, "label") == 0) {
+ str = xml_node_get_attr_value (node, "name");
+ if (str == NULL) {
+ /* TODO: report error */
+ return;
+ }
+
+ animation_add_element_label (animation, str, line_no);
+ } else
+
+ if (g_strcmp0 (name, "goto") == 0) {
+ str = xml_node_get_attr_value (node, "label");
+ if (str == NULL) {
+ /* TODO: report error */
+ return;
+ }
+
+ animation_add_element_goto (
+ animation, str, rand, line_no
+ );
+ } else
+
+ if (g_strcmp0 (name, "sequence") == 0) {
+ animation_add_sequence (
+ animation,
+ imageset,
+ node,
+ offsetX, offsetY,
+ delay, rand,
+ line_no
+ );
+ } else
+
+ {
+ /* TODO: unknown tag */
+ return;
+ }
+}
+
+Animation *
+animation_new (
+ const Imageset *imageset,
+ const XMLNode *node,
+ gint included_from
+) {
+ Animation *animation;
+ gchar *dir;
+ GList *sub_node;
+
+ g_return_val_if_fail (g_strcmp0 (node->name, "animation") == 0, NULL);
+
+ dir = xml_node_get_attr_value (node, "direction");
+
+ if (dir == NULL)
+ dir = "";
+
+ animation = (Animation *) g_new0 (Animation, 1);
+ animation->direction = dir;
+
+ sub_node = node->sub_nodes;
+ while (sub_node != NULL) {
+ animation_add_elements_from_node (
+ animation,
+ imageset,
+ (XMLNode *) sub_node->data,
+ included_from
+ );
+ sub_node = g_list_next (sub_node);
+ }
+
+ return animation;
+}
+
+gint
+animation_compare_by_direction (
+ const Animation *first,
+ const Animation *second
+) {
+ return g_strcmp0 (first->direction, second->direction);
+}
+
+gboolean
+animation_direction_equals (
+ const Animation *animation,
+ const gchar *direction
+) {
+ return g_strcmp0 (animation->direction, direction) == 0;
+}
+
+void
+animation_free (Animation *animation) {
+ g_list_free_full (
+ animation->elements,
+ (GDestroyNotify) g_free
+ );
+
+ g_free (animation->direction);
+ g_free (animation);
+}
+
+AnimElement *
+animation_element_new () {
+ return (AnimElement *) g_new0 (AnimElement, 1);
+}
diff --git a/saedit/animation.h b/saedit/animation.h
new file mode 100644
index 0000000..6c4d05f
--- /dev/null
+++ b/saedit/animation.h
@@ -0,0 +1,59 @@
+#ifndef ANIMATION_H
+#define ANIMATION_H
+
+#include <gdk/gdk.h>
+#include <glib.h>
+#include "common.h"
+#include "xml.h"
+#include "imageset.h"
+
+typedef struct {
+ gchar *direction;
+ GList *elements;
+} Animation;
+
+Animation *
+animation_new (
+ const Imageset *imageset,
+ const XMLNode *node,
+ gint included_from
+);
+
+void
+animation_free (Animation *animation);
+
+gint
+animation_compare_by_direction (
+ const Animation *first,
+ const Animation *second
+);
+
+gboolean
+animation_direction_equals (
+ const Animation *animation,
+ const gchar *direction
+);
+
+enum {
+ ELEMENT_END = 0,
+ ELEMENT_FRAME,
+ ELEMENT_JUMP,
+ ELEMENT_GOTO,
+ ELEMENT_PAUSE,
+ ELEMENT_LABEL,
+ ELEMENT_COUNT
+};
+
+typedef struct {
+ gint type;
+ gint delay;
+ gint offsetX, offsetY;
+ gint rand;
+ gchar *str;
+ gint line_no;
+ GdkPixbuf *sprite;
+} AnimElement;
+
+AnimElement *animation_element_new (void);
+
+#endif
diff --git a/saedit/buffer.c b/saedit/buffer.c
new file mode 100644
index 0000000..5c51bcc
--- /dev/null
+++ b/saedit/buffer.c
@@ -0,0 +1,87 @@
+#include <gtksourceview/gtksource.h>
+#include "main.h"
+#include "buffer.h"
+
+gchar *
+buffer_get_text () {
+ GtkTextIter start, end;
+ GtkTextBuffer *buffer =
+ GTK_TEXT_BUFFER (gtk_text_view_get_buffer (
+ GTK_TEXT_VIEW (source_view)
+ ));
+
+ gtk_text_buffer_get_start_iter (buffer, &start);
+ gtk_text_buffer_get_end_iter (buffer, &end);
+
+ return gtk_text_buffer_get_text (buffer, &start, &end, TRUE);
+}
+
+void
+buffer_set_text (
+ const gchar *text,
+ const guint len
+) {
+ GtkTextBuffer *buffer =
+ GTK_TEXT_BUFFER (gtk_text_view_get_buffer (
+ GTK_TEXT_VIEW (source_view)
+ ));
+
+ gtk_text_buffer_set_text (buffer, text, len);
+}
+
+void
+buffer_mark_line (gint line_no) {
+ GtkTextIter iter, end;
+ GtkSourceBuffer *buffer = GTK_SOURCE_BUFFER (
+ gtk_text_view_get_buffer (GTK_TEXT_VIEW (source_view))
+ );
+
+ gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter);
+ gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (buffer), &end);
+
+ gtk_source_buffer_remove_source_marks (
+ buffer,
+ &iter,
+ &end,
+ "active-line"
+ );
+
+ if (line_no == -1)
+ return;
+
+ gtk_text_iter_set_line (&iter, line_no);
+ gtk_text_view_scroll_to_mark (
+ GTK_TEXT_VIEW (source_view),
+ (GtkTextMark *) gtk_source_buffer_create_source_mark (
+ buffer,
+ NULL,
+ "active-line",
+ &iter
+ ),
+ 0.0,
+ TRUE,
+ 0.0,
+ 0.5
+ );
+}
+
+
+void
+buffer_set_modified (gboolean modified) {
+ GtkTextBuffer *buffer =
+ GTK_TEXT_BUFFER (gtk_text_view_get_buffer (
+ GTK_TEXT_VIEW (source_view)
+ ));
+
+ gtk_text_buffer_set_modified (buffer, modified);
+}
+
+gboolean
+buffer_get_modified () {
+ GtkTextBuffer *buffer =
+ GTK_TEXT_BUFFER (gtk_text_view_get_buffer (
+ GTK_TEXT_VIEW (source_view)
+ ));
+
+ return gtk_text_buffer_get_modified (buffer);
+}
diff --git a/saedit/buffer.h b/saedit/buffer.h
new file mode 100644
index 0000000..2926687
--- /dev/null
+++ b/saedit/buffer.h
@@ -0,0 +1,22 @@
+#ifndef _BUFFER_H_
+#define _BUFFER_H_
+
+gchar *
+buffer_get_text (void);
+
+void
+buffer_set_text (
+ const gchar *text,
+ const guint len
+);
+
+void
+buffer_mark_line (gint line_no);
+
+void
+buffer_set_modified (gboolean modified);
+
+gboolean
+buffer_get_modified (void);
+
+#endif
diff --git a/saedit/callbacks.c b/saedit/callbacks.c
new file mode 100644
index 0000000..f27fc47
--- /dev/null
+++ b/saedit/callbacks.c
@@ -0,0 +1,432 @@
+#include "main.h"
+#include "errors.h"
+#include "config.h"
+#include "file.h"
+#include "buffer.h"
+#include <string.h>
+
+void
+store_append_action (const Action *action, gpointer user_data) {
+ int hp;
+ gchar *name;
+ GtkTreeIter iter;
+
+ action_get_hp_and_name (action, &hp, &name);
+ gtk_list_store_append (store_actions, &iter);
+
+ gtk_list_store_set (
+ store_actions,
+ &iter,
+ 0, name,
+ 1, hp,
+ 2, get_action_id (hp, name),
+ -1
+ );
+}
+
+void
+parse_buffer_clicked_cb (
+ GtkToolButton *button,
+ gpointer user_data
+) {
+ gchar *buffer_text;
+ GError *error = NULL;
+ GList *actions;
+
+ release_context ();
+
+ buffer_text = buffer_get_text ();
+
+ context = sprite_context_new (
+ config_keys_get_data_folder_path ()
+ );
+ interactor = interactor_new (context);
+
+ sprite_context_add_sprite (
+ context,
+ xml_parse_buffer (buffer_text, &error),
+ -1
+ );
+
+ if (error != NULL) {
+ post_error ("XML parser", error->message);
+ g_error_free (error);
+ }
+
+ g_free (buffer_text);
+
+ interactor_set_updated_callback (interactor, intr_updated);
+
+ actions = sprite_context_get_actions (context);
+ g_list_foreach (actions, (GFunc) store_append_action, NULL);
+ g_list_free (actions);
+
+ gtk_widget_queue_draw (d_area);
+}
+
+void
+append_direction (const gchar *direction, gpointer user_data) {
+ gchar *display;
+
+ g_return_if_fail (direction != NULL);
+
+ if (strlen (direction) == 0)
+ display = g_strdup ("<empty>");
+ else
+ display = g_strdup (direction);
+
+ gtk_combo_box_text_append (
+ cb_directions,
+ direction,
+ display
+ );
+
+ g_free (display);
+}
+
+void
+action_changed_cb (GtkComboBox *cbox, gpointer user_data) {
+ GList *directions;
+ GtkTreeIter iter;
+ gint hp;
+ gchar *name;
+ const Action *action;
+ gboolean w_run;
+
+ g_return_if_fail (cbox == cb_actions);
+
+ if (interactor == NULL)
+ return;
+
+ if (!gtk_combo_box_get_active_iter (cbox, &iter))
+ return;
+
+ gtk_tree_model_get (
+ GTK_TREE_MODEL (store_actions),
+ &iter,
+ 0, &name,
+ 1, &hp,
+ -1
+ );
+
+ w_run = interactor_loop_running (interactor);
+ if (w_run)
+ interactor_loop_stop (interactor);
+
+ gtk_combo_box_text_remove_all (cb_directions);
+
+ action = sprite_context_get_action (context, hp, name);
+ g_return_if_fail (action != NULL);
+
+ directions = action_get_directions (action);
+ g_list_foreach (directions, (GFunc) append_direction, NULL);
+ g_list_free (directions);
+
+ interactor_set_action (interactor, hp, name);
+
+ if (w_run)
+ interactor_loop_start (interactor, 10, 10);
+}
+
+void
+direction_changed_cb (GtkComboBox *cbox, gpointer user_data) {
+ gchar *active_text;
+
+ g_return_if_fail (cbox == GTK_COMBO_BOX (cb_directions));
+
+ if (interactor == NULL)
+ return;
+
+ active_text = gtk_combo_box_text_get_active_text (cb_directions);
+
+ if (active_text == NULL)
+ return;
+
+ interactor_set_direction (
+ interactor,
+ active_text
+ );
+}
+
+void
+play_pause_clicked_cb (
+ GtkToolButton *button,
+ gpointer user_data
+) {
+ if (interactor == NULL)
+ return;
+
+ if (interactor_loop_running (interactor))
+ interactor_loop_stop (interactor);
+ else
+ interactor_loop_start (interactor, 10, 10);
+}
+
+void
+next_frame_clicked_cb (
+ GtkToolButton *button,
+ gpointer user_data
+) {
+ if (interactor == NULL)
+ return;
+
+ interactor_skip_current_frame (interactor);
+}
+
+void
+first_frame_clicked_cb (
+ GtkToolButton *button,
+ gpointer user_data
+) {
+ if (interactor == NULL)
+ return;
+
+ interactor_loop_stop (interactor);
+ interactor_reset_animation (interactor);
+}
+
+void
+choose_df_activated_cb (
+ GtkMenuItem *item,
+ gpointer user_data
+) {
+ TreeFolderView *tfview;
+ GtkWidget *dialog;
+ gint res;
+ gchar *filename;
+
+ g_return_if_fail (IS_TREE_FOLDER_VIEW (user_data));
+
+ tfview = TREE_FOLDER_VIEW (user_data);
+ dialog = gtk_file_chooser_dialog_new (
+ "Choose data folder",
+ GTK_WINDOW (main_window),
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
+ "Cancel", GTK_RESPONSE_CANCEL,
+ "Open", GTK_RESPONSE_ACCEPT,
+ NULL
+ );
+
+ gtk_file_chooser_set_filename (
+ GTK_FILE_CHOOSER (dialog),
+ tree_folder_view_get_filename (tfview)
+ );
+
+ res = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ if (res == GTK_RESPONSE_ACCEPT) {
+ filename = gtk_file_chooser_get_filename (
+ GTK_FILE_CHOOSER (dialog)
+ );
+
+ config_keys_set_data_folder_path (filename);
+ config_keys_save ();
+
+ tree_folder_view_set_filename (
+ tfview,
+ filename
+ );
+
+ g_free (filename);
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+void
+open_file_activated_cb (
+ GtkMenuItem *item,
+ gpointer user_data
+) {
+ GtkWidget *dialog;
+ GtkFileChooser *chooser;
+ gint res;
+ gchar *data_folder;
+
+ dialog = gtk_file_chooser_dialog_new (
+ NULL,
+ GTK_WINDOW (main_window),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ "_Cancel", GTK_RESPONSE_CANCEL,
+ "_Open", GTK_RESPONSE_ACCEPT,
+ NULL
+ );
+
+ data_folder = config_keys_get_data_folder_path ();
+
+ chooser = GTK_FILE_CHOOSER (dialog);
+ gtk_file_chooser_set_current_folder (
+ chooser,
+ data_folder
+ );
+
+ res = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ if (res == GTK_RESPONSE_ACCEPT) {
+ gchar *filename = gtk_file_chooser_get_filename (chooser);
+ open_file (filename);
+ g_free (filename);
+ }
+
+ gtk_widget_destroy (dialog);
+ g_free (data_folder);
+}
+
+void save_file_as_activated_cb (
+ GtkMenuItem *item,
+ gpointer user_data
+) {
+ GtkWidget *dialog;
+ GtkFileChooser *chooser;
+ gint res;
+ gchar *data_folder;
+
+ dialog = gtk_file_chooser_dialog_new (
+ NULL,
+ GTK_WINDOW (main_window),
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ "_Cancel", GTK_RESPONSE_CANCEL,
+ "_Save", GTK_RESPONSE_ACCEPT,
+ NULL
+ );
+
+ data_folder = config_keys_get_data_folder_path ();
+
+ chooser = GTK_FILE_CHOOSER (dialog);
+ gtk_file_chooser_set_do_overwrite_confirmation (chooser, TRUE);
+
+ if (get_opened_file_name () != NULL) {
+ gtk_file_chooser_set_current_name (
+ chooser,
+ get_opened_file_name ()
+ );
+ } else {
+ gtk_file_chooser_set_current_folder (
+ chooser,
+ data_folder
+ );
+ }
+
+ res = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ if (res == GTK_RESPONSE_ACCEPT) {
+ gchar *filename = gtk_file_chooser_get_filename (chooser);
+ save_file (filename);
+ g_free (filename);
+ }
+
+ gtk_widget_destroy (dialog);
+ g_free (data_folder);
+}
+
+void
+save_file_activated_cb (
+ GtkMenuItem *item,
+ gpointer user_data
+) {
+ const gchar *filename;
+
+ filename = get_opened_file_name ();
+
+ if (filename == NULL) {
+ save_file_as_activated_cb (item, user_data);
+ return;
+ }
+
+ save_file (NULL);
+}
+
+void
+zoom_adjustment_value_changed_cb (
+ GtkAdjustment *adjustment,
+ gpointer user_data
+) {
+ sprite_drawing_area_set_scale_factor (
+ SPRITE_DRAWING_AREA (user_data),
+ gtk_adjustment_get_value (adjustment)
+ );
+ gtk_widget_queue_draw (GTK_WIDGET (user_data));
+}
+
+void
+tfview_file_activated_cb (
+ TreeFolderView *tfview,
+ gchar *filename,
+ gpointer user_data
+) {
+ open_file (filename);
+}
+
+void
+sourceview_buffer_modified_changed_cb (
+ GtkTextBuffer *textbuffer,
+ gpointer user_data
+) {
+ update_window_title ();
+}
+
+void
+new_file_activated_cb (
+ GtkMenuItem *item,
+ gpointer user_data
+) {
+ open_file (NULL);
+}
+
+void
+view_reset_to_center_activate_cb (
+ GtkMenuItem *item,
+ gpointer user_data
+) {
+ sprite_drawing_area_set_center (
+ SPRITE_DRAWING_AREA (d_area),
+ 0, 0
+ );
+
+ gtk_widget_queue_draw (d_area);
+}
+
+void
+show_tile_grid_toggled_cb (
+ GtkCheckMenuItem *item,
+ gpointer user_data
+) {
+ sda_layer_set_visible (
+ tile_grid_layer,
+ gtk_check_menu_item_get_active (item)
+ );
+
+ gtk_widget_queue_draw (d_area);
+}
+
+void
+show_pixel_grid_toggled_cb (
+ GtkCheckMenuItem *item,
+ gpointer user_data
+) {
+ sda_layer_set_visible (
+ pixel_grid_layer,
+ gtk_check_menu_item_get_active (item)
+ );
+
+ gtk_widget_queue_draw (d_area);
+}
+
+void
+menuitem_about_activate_cb (
+ GtkMenuItem *item,
+ gpointer user_data
+) {
+ gtk_dialog_run (GTK_DIALOG (user_data));
+}
+
+void
+about_dialog_response_cb (
+ GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data
+) {
+ if (response_id == GTK_RESPONSE_DELETE_EVENT) {
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ }
+}
diff --git a/saedit/callbacks.h b/saedit/callbacks.h
new file mode 100644
index 0000000..7107353
--- /dev/null
+++ b/saedit/callbacks.h
@@ -0,0 +1,96 @@
+#ifndef _CALLBACKS_H_
+#define _CALLBACKS_H_
+
+void
+parse_buffer_clicked_cb (
+ GtkToolButton *button,
+ gpointer user_data
+);
+
+void
+action_changed_cb (GtkComboBox *cbox, gpointer user_data);
+
+void
+direction_changed_cb (GtkComboBox *cbox, gpointer user_data) {
+
+void
+play_pause_clicked_cb (
+ GtkToolButton *button,
+ gpointer user_data
+);
+
+void
+first_frame_clicked_cb (
+ GtkToolButton *button,
+ gpointer user_data
+);
+
+void
+choose_df_activated_cb (
+ GtkMenuItem *item,
+ gpointer user_data
+);
+
+void
+open_file_activated_cb (
+ GtkMenuIterm *item,
+ gpointer user_data
+);
+
+void
+save_file_activated_cb (
+ GtkMenuIterm *item,
+ gpointer user_data
+);
+
+void
+zoom_adjustment_value_changed_cb (
+ GtkAdjustment *adjustment,
+ gpointer user_data
+);
+
+void
+tfview_file_activated_cb (
+ TreeFolderView *tfview,
+ gchar *filename,
+ gpointer user_data
+);
+
+void
+new_file_activated_cb (
+ GtkMenuItem *item,
+ gpointer user_data
+);
+
+void
+save_file_as_activated_cb (
+ GtkMenuItem *item,
+ gpointer user_data
+);
+
+void
+show_tile_grid_toggled_cb (
+ GtkCheckMenuItem *item,
+ gpointer user_data
+);
+
+void
+show_pixel_grid_toggled_cb (
+ GtkCheckMenuItem *item,
+ gpointer user_data
+);
+
+void
+menuitem_about_activate_cb (
+ GtkMenuItem *item,
+ gpointer user_data
+);
+
+void
+about_dialog_response_cb (
+ GtkDialog *dialog,
+ gint response_id,
+ gpointer user_data
+);
+
+#endif
diff --git a/saedit/common.c b/saedit/common.c
new file mode 100644
index 0000000..9bb8a30
--- /dev/null
+++ b/saedit/common.c
@@ -0,0 +1,22 @@
+#include "common.h"
+
+#include <stdlib.h>
+#include <errno.h>
+
+gboolean
+try_strtoint (
+ const gchar *str,
+ gint *result
+) {
+ gchar *endptr;
+ gint retval;
+
+ errno = 0;
+ retval = strtol (str, &endptr, 10);
+
+ if (*endptr != 0 || errno != 0)
+ return FALSE;
+
+ *result = retval;
+ return TRUE;
+}
diff --git a/saedit/common.h b/saedit/common.h
new file mode 100644
index 0000000..1715e41
--- /dev/null
+++ b/saedit/common.h
@@ -0,0 +1,12 @@
+#ifndef COMMON_H
+#define COMMON_H
+
+#include <glib.h>
+
+gboolean
+try_strtoint (
+ const gchar *str,
+ gint *result
+);
+
+#endif
diff --git a/saedit/config.c b/saedit/config.c
new file mode 100644
index 0000000..1e20411
--- /dev/null
+++ b/saedit/config.c
@@ -0,0 +1,191 @@
+#include "config.h"
+#include "xml.h"
+#include <unistd.h>
+#include <sys/stat.h>
+
+static GKeyFile *_config_keyfile = NULL;
+
+gchar *
+_config_get_config_folder (void) {
+ return g_strjoin (
+ "/",
+ g_get_user_config_dir (),
+ "saedit2",
+ NULL
+ );
+}
+
+gchar *
+_config_get_key_file_path (void) {
+ gchar *dir = _config_get_config_folder ();
+ gchar *result;
+
+ result = g_strjoin (
+ "/",
+ dir,
+ "config.ini",
+ NULL
+ );
+
+ g_free (dir);
+ return result;
+}
+
+void
+config_keys_load () {
+ gchar *filename;
+
+ _config_keyfile = g_key_file_new ();
+
+ filename = _config_get_key_file_path ();
+
+ g_key_file_load_from_file (
+ _config_keyfile,
+ filename,
+ 0,
+ NULL
+ );
+
+ g_free (filename);
+}
+
+gchar *
+config_keys_get_data_folder_path () {
+ gchar *result;
+
+ if (_config_keyfile == NULL)
+ config_keys_load ();
+
+ result = g_key_file_get_value (
+ _config_keyfile,
+ "Default",
+ "Data Folder",
+ NULL
+ );
+
+ if (result == NULL)
+ result = g_strdup ("");
+
+ return result;
+}
+
+void
+config_keys_set_data_folder_path (const gchar *filename) {
+ if (_config_keyfile == NULL)
+ config_keys_load ();
+
+ g_key_file_set_value (
+ _config_keyfile,
+ "Default",
+ "Data Folder",
+ filename
+ );
+}
+
+gint
+config_keys_get_tile_size () {
+ /* TODO */
+ return 32;
+}
+
+void
+config_keys_save () {
+ gchar *filename;
+
+ if (_config_keyfile == NULL)
+ return;
+
+ filename = _config_get_config_folder ();
+ mkdir (filename, S_IRWXU);
+ g_free (filename);
+
+ filename = _config_get_key_file_path ();
+
+ g_key_file_save_to_file (
+ _config_keyfile,
+ filename,
+ NULL
+ );
+
+ g_free (filename);
+}
+
+static XMLNode *_config_paths_xml_root = NULL;
+
+void
+config_data_paths_load () {
+ gchar *filename;
+ gchar *df_path;
+
+ df_path = config_keys_get_data_folder_path ();
+
+ filename = g_strjoin (
+ "/",
+ df_path,
+ "paths.xml",
+ NULL
+ );
+
+ _config_paths_xml_root = xml_parse_file (filename);
+
+ g_free (df_path);
+ g_free (filename);
+}
+
+gchar *
+config_data_paths_get_sprites_path () {
+ XMLNode *node;
+ GList *list;
+ gchar *name;
+
+ if (_config_paths_xml_root == NULL)
+ config_data_paths_load ();
+
+ node = _config_paths_xml_root;
+
+ if (node != NULL) {
+ list = node->sub_nodes;
+ while (TRUE) {
+ list = g_list_find_custom (
+ list,
+ "option",
+ xml_node_compare_with_name_func
+ );
+
+ if (list == NULL)
+ break;
+
+ name = xml_node_get_attr_value (list->data, "name");
+ if (name != NULL && g_strcmp0 (name, "sprites") == 0) {
+ g_free (name);
+ return xml_node_get_attr_value (list->data, "value");
+ }
+
+ g_free(name);
+ list = list->next;
+ }
+ }
+
+ return g_strdup ("");
+}
+
+gchar *
+config_data_path_get_full_sprite_path (const gchar *rel_path) {
+ gchar *data_folder, *sprites_path, *filename;
+
+ data_folder = config_keys_get_data_folder_path ();
+ sprites_path = config_data_paths_get_sprites_path ();
+
+ filename = g_strjoin (
+ "/",
+ data_folder,
+ sprites_path,
+ rel_path,
+ NULL
+ );
+
+ g_free (sprites_path);
+ g_free (data_folder);
+
+ return filename;
+}
diff --git a/saedit/config.h b/saedit/config.h
new file mode 100644
index 0000000..cae98bd
--- /dev/null
+++ b/saedit/config.h
@@ -0,0 +1,32 @@
+#ifndef _CONFIG_H_
+#define _CONFIG_H_
+
+#include <glib.h>
+
+void
+config_keys_load (void);
+
+void
+config_keys_save (void);
+
+gchar *
+config_keys_get_data_folder_path (void);
+
+void
+config_keys_set_data_folder_path (
+ const gchar *filename
+);
+
+gint
+config_keys_get_tile_size (void);
+
+void
+config_data_paths_load (void);
+
+gchar *
+config_data_paths_get_sprites_path (void);
+
+gchar *
+config_data_path_get_full_sprite_path (const gchar *rel_path);
+
+#endif
diff --git a/saedit/context.c b/saedit/context.c
new file mode 100644
index 0000000..2a14de5
--- /dev/null
+++ b/saedit/context.c
@@ -0,0 +1,227 @@
+#include <gtk/gtk.h>
+#include "context.h"
+#include "imageset.h"
+#include "action.h"
+#include "animation.h"
+#include "xml.h"
+#include "config.h"
+#include "xmlsetup.h"
+
+struct _SpriteContext {
+ gchar *cdf_filename;
+ GList *imagesets;
+ GList *actions;
+ GList *includes;
+};
+
+SpriteContext *
+sprite_context_new (
+ const gchar *cdf_filename
+) {
+ SpriteContext *context = (SpriteContext *) g_new0 (SpriteContext, 1);
+ context->cdf_filename = g_strdup (cdf_filename);
+ return context;
+}
+
+void
+sprite_context_add_imageset (
+ SpriteContext *context,
+ XMLNode *node
+) {
+ Imageset *imgset;
+
+ g_return_if_fail (g_strcmp0 (node->name, "imageset") == 0);
+
+ imgset = imageset_new (node, context->cdf_filename);
+ if (imgset == NULL)
+ return;
+
+ if (g_list_find_custom (
+ context->imagesets, imgset,
+ (GCompareFunc) imageset_compare_by_name) != NULL
+ ) {
+ imageset_free (imgset);
+ return;
+ }
+
+ context->imagesets = g_list_append (context->imagesets, imgset);
+}
+
+void
+sprite_context_add_action (
+ SpriteContext *context,
+ XMLNode *node,
+ gint included_from
+) {
+ Action *action;
+
+ g_return_if_fail (g_strcmp0 (node->name, "action") == 0);
+
+ action = action_new (context, node, included_from);
+ if (action == NULL)
+ return;
+
+ if (g_list_find_custom (
+ context->actions, action,
+ (GCompareFunc) action_compare_by_hp_and_name) != NULL
+ ) {
+ action_free (action);
+ return;
+ }
+
+ context->actions = g_list_append (context->actions, action);
+}
+
+void
+sprite_context_add_sprite (
+ SpriteContext *context,
+ XMLNode *node,
+ gint included_from
+) {
+ GList *list;
+ g_return_if_fail (node != NULL);
+ g_return_if_fail (g_strcmp0 (node->name, "sprite") == 0);
+
+ list = node->sub_nodes;
+ for (; list != NULL; list = g_list_next (list)) {
+ XMLNode *current = (XMLNode *) list->data;
+ gchar *name = current->name;
+ if (g_strcmp0 (name, "include") == 0) {
+ XMLNode *sprite;
+ gchar *filename, *file;
+
+ file = xml_node_get_attr_value (current, "file");
+ if (file == NULL) {
+ /* TODO: report error */
+ continue;
+ }
+
+ if (g_list_find_custom (
+ context->includes, file,
+ (GCompareFunc) g_strcmp0) != NULL
+ ) {
+ /* TODO: such file was already included */
+ g_free (file);
+ continue;
+ }
+
+ context->includes = g_list_append (
+ context->includes, file
+ );
+
+ filename = config_data_path_get_full_sprite_path (file);
+
+ sprite = xml_parse_file (filename);
+ g_free (filename);
+
+ if (sprite == NULL) {
+ /* TODO: report error */
+ continue;
+ }
+
+ if (g_strcmp0 (sprite->name, "sprite") != 0) {
+ /* TODO: report error */
+ continue;
+ }
+
+ if (included_from == -1)
+ included_from = current->line_no;
+
+ sprite_context_add_sprite (
+ context,
+ sprite,
+ included_from
+ );
+
+ xml_free (sprite);
+ } else
+ if (g_strcmp0 (name, "imageset") == 0) {
+ sprite_context_add_imageset (context, current);
+ } else
+ if (g_strcmp0 (name, "action") == 0) {
+ sprite_context_add_action (context, current, included_from);
+ } else
+ if (g_strcmp0 (name, "saedit") == 0) {
+ if (included_from == -1) { /* we are in the main context */
+ xml_setup_setup (current);
+ }
+ } else {
+ /* TODO: sprite contains something unknown */
+ }
+ }
+}
+
+Action *
+sprite_context_get_action (
+ const SpriteContext *context,
+ gint hp,
+ const gchar *name
+) {
+ GList *action = context->actions;
+
+ while (action != NULL) {
+ if (action_hp_and_name_equals (
+ (Action *) action->data,
+ hp,
+ name
+ )) {
+ return (Action *) action->data;
+ }
+
+ action = g_list_next (action);
+ }
+
+ return NULL;
+}
+
+Imageset *
+sprite_context_get_imageset (
+ const SpriteContext *context,
+ const gchar *name
+) {
+ GList *imageset = context->imagesets;
+
+ while (imageset != NULL) {
+ if (imageset_name_equals (
+ (Imageset *) imageset->data,
+ name
+ )) {
+ return (Imageset *) imageset->data;
+ }
+
+ imageset = g_list_next (imageset);
+ }
+
+ return NULL;
+}
+
+GList *
+sprite_context_get_actions (
+ const SpriteContext *context
+) {
+ return g_list_copy (context->actions);
+}
+
+void
+sprite_context_free (
+ SpriteContext *context
+) {
+ g_list_free_full (
+ context->imagesets,
+ (GDestroyNotify) imageset_free
+ );
+
+ g_list_free_full (
+ context->actions,
+ (GDestroyNotify) action_free
+ );
+
+ g_list_free_full (
+ context->includes,
+ (GDestroyNotify) g_free
+ );
+
+ g_free (context->cdf_filename);
+
+ g_free (context);
+}
diff --git a/saedit/context.h b/saedit/context.h
new file mode 100644
index 0000000..9d41fde
--- /dev/null
+++ b/saedit/context.h
@@ -0,0 +1,50 @@
+#ifndef CONTEXT_H
+#define CONTEXT_H
+
+#include "xml.h"
+#include "imageset.h"
+#include "action.h"
+
+typedef struct _SpriteContext SpriteContext;
+
+SpriteContext *
+sprite_context_new (
+ const gchar *client_data_folder
+);
+
+void
+sprite_context_add_sprite (
+ SpriteContext *context,
+ XMLNode *node,
+ gboolean is_include
+);
+
+Action *
+action_new (
+ const SpriteContext *context,
+ const XMLNode *node,
+ gint included_from
+);
+
+Action *
+sprite_context_get_action (
+ const SpriteContext *context,
+ gint hp,
+ const gchar *name
+);
+
+Imageset *
+sprite_context_get_imageset (
+ const SpriteContext *context,
+ const gchar *name
+);
+
+GList *
+sprite_context_get_actions (
+ const SpriteContext *context
+);
+
+void
+sprite_context_free (SpriteContext *context);
+
+#endif
diff --git a/saedit/drawfuncs.c b/saedit/drawfuncs.c
new file mode 100644
index 0000000..97a769e
--- /dev/null
+++ b/saedit/drawfuncs.c
@@ -0,0 +1,107 @@
+#include "interactor.h"
+#include "drawfuncs.h"
+#include "spritedrawingarea.h"
+#include "config.h"
+
+void
+interactor_sprite_layer_draw_func (
+ SDALayer *layer,
+ cairo_t *cr,
+ gpointer user_data
+) {
+ const GdkPixbuf *sprite;
+ gint offsetX, offsetY;
+ Interactor *interactor = *((Interactor **) user_data);
+
+ if (interactor == NULL)
+ return;
+
+ sprite = interactor_get_sprite (interactor);
+
+ if (sprite == NULL)
+ return;
+
+ interactor_get_offset (interactor, &offsetX, &offsetY);
+
+ gdk_cairo_set_source_pixbuf (
+ cr, sprite,
+ offsetX,
+ offsetY + config_keys_get_tile_size () / 2
+ );
+ cairo_pattern_set_filter (
+ cairo_get_source (cr),
+ CAIRO_FILTER_NEAREST
+ );
+ cairo_paint (cr);
+}
+
+void
+background_layer_draw_func (
+ SDALayer *layer,
+ cairo_t *cr,
+ gpointer user_data
+) {
+ GdkRGBA *rgba = (GdkRGBA *) user_data;
+
+ gdk_cairo_set_source_rgba (cr, rgba);
+
+ cairo_rectangle (
+ cr,
+ -SPRITE_DRAWING_AREA_FIELD_SIZE,
+ -SPRITE_DRAWING_AREA_FIELD_SIZE,
+ 2 * SPRITE_DRAWING_AREA_FIELD_SIZE,
+ 2 * SPRITE_DRAWING_AREA_FIELD_SIZE
+ );
+ cairo_fill (cr);
+}
+
+void
+tile_grid_layer_draw_func (
+ SDALayer *layer,
+ cairo_t *cr,
+ gpointer user_data
+) {
+ gint x;
+ gint field_size = SPRITE_DRAWING_AREA_FIELD_SIZE;
+ gint tile_size = config_keys_get_tile_size ();
+
+ cairo_set_line_width (cr, 1.0);
+ cairo_set_operator (cr, CAIRO_OPERATOR_XOR);
+ cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
+ cairo_translate (cr, 0.5, 0.5);
+
+ x = -tile_size / 2;
+ while (x >= -field_size)
+ x -= tile_size;
+
+ for (; x <= field_size; x += tile_size) {
+ cairo_move_to (cr, x, -field_size);
+ cairo_line_to (cr, x, field_size);
+ cairo_move_to (cr, -field_size, x);
+ cairo_line_to (cr, field_size, x);
+ }
+
+ cairo_stroke (cr);
+}
+
+void
+pixel_grid_layer_draw_func (
+ SDALayer *layer,
+ cairo_t *cr,
+ gpointer user_data
+) {
+ gint x;
+ gint field_size = SPRITE_DRAWING_AREA_FIELD_SIZE;
+
+ cairo_set_line_width (cr, 0.1);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+
+ for (x = -field_size; x <= field_size; ++x) {
+ cairo_move_to (cr, x, -field_size);
+ cairo_line_to (cr, x, field_size);
+ cairo_move_to (cr, -field_size, x);
+ cairo_line_to (cr, field_size, x);
+ }
+
+ cairo_stroke (cr);
+}
diff --git a/saedit/drawfuncs.h b/saedit/drawfuncs.h
new file mode 100644
index 0000000..1c1154e
--- /dev/null
+++ b/saedit/drawfuncs.h
@@ -0,0 +1,33 @@
+#ifndef _DRAW_FUNCS_H_
+#define _DRAW_FUNCS_H_
+
+#include "spritedrawingarea.h"
+
+void
+interactor_sprite_layer_draw_func (
+ SDALayer *layer,
+ cairo_t *cr,
+ gpointer user_data
+);
+
+void
+background_layer_draw_func (
+ SDALayer *layer,
+ cairo_t *cr,
+ gpointer user_data
+);
+
+void
+tile_grid_layer_draw_func (
+ SDALayer *layer,
+ cairo_t *cr,
+ gpointer user_data
+);
+
+void
+pixel_grid_layer_draw_func (
+ SDALayer *layer,
+ cairo_t *cr,
+ gpointer user_data
+);
+#endif
diff --git a/saedit/errors.c b/saedit/errors.c
new file mode 100644
index 0000000..ff3a710
--- /dev/null
+++ b/saedit/errors.c
@@ -0,0 +1,23 @@
+#include "errors.h"
+#include "main.h"
+
+void
+post_error (
+ const gchar *error_context,
+ const gchar *error_message
+) {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new_with_markup (
+ GTK_WINDOW (main_window),
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "<b>%s error:</b> %s",
+ error_context,
+ error_message
+ );
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+}
diff --git a/saedit/errors.h b/saedit/errors.h
new file mode 100644
index 0000000..58b12e6
--- /dev/null
+++ b/saedit/errors.h
@@ -0,0 +1,12 @@
+#ifndef _ERRORS_H_
+#define _ERRORS_H_
+
+#include <glib.h>
+
+void
+post_error (
+ const gchar *error_context,
+ const gchar *error_message
+);
+
+#endif
diff --git a/saedit/file.c b/saedit/file.c
new file mode 100644
index 0000000..5496c86
--- /dev/null
+++ b/saedit/file.c
@@ -0,0 +1,103 @@
+#include <gtksourceview/gtksource.h>
+#include "errors.h"
+#include "buffer.h"
+#include "main.h"
+#include "file.h"
+
+#include <string.h>
+
+
+gchar *opened_file_name = NULL;
+
+gboolean
+show_unsaved_changes_dialog () {
+ gint result;
+ GtkWidget *dialog;
+
+ if (!buffer_get_modified ()) {
+ return FALSE;
+ }
+
+ dialog = gtk_message_dialog_new (
+ GTK_WINDOW (main_window),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_WARNING,
+ GTK_BUTTONS_YES_NO,
+ "There are unsaved changes in the current file. "
+ "Do you wish to proceed?"
+ );
+
+ result = gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+
+ return result == GTK_RESPONSE_NO;
+}
+
+void
+open_file (
+ const gchar *filename
+) {
+ gsize len;
+ gchar *text;
+
+ if (show_unsaved_changes_dialog ())
+ return;
+
+ release_context ();
+
+ if (filename != NULL) {
+ g_file_get_contents (
+ filename,
+ &text,
+ &len,
+ NULL
+ );
+ } else {
+ text = g_strdup ("");
+ len = 0;
+ }
+
+ g_free (opened_file_name);
+ opened_file_name = g_strdup (filename);
+
+ buffer_set_text (text, len);
+ buffer_set_modified (FALSE);
+
+ update_window_title ();
+
+ g_free (text);
+}
+
+void
+save_file (const gchar *filename) {
+ GError *error = NULL;
+ gboolean success;
+ gchar *text = buffer_get_text ();
+
+ success = g_file_set_contents (
+ filename == NULL ? opened_file_name : filename,
+ text,
+ strlen (text),
+ &error
+ );
+
+ if (!success) {
+ post_error ("Saving", error->message);
+ } else {
+ if (filename != NULL) {
+ g_free (opened_file_name);
+ opened_file_name = g_strdup (filename);
+ }
+
+ buffer_set_modified (FALSE);
+ update_window_title ();
+ }
+
+ g_free (text);
+}
+
+const gchar *
+get_opened_file_name () {
+ return opened_file_name;
+}
+
diff --git a/saedit/file.h b/saedit/file.h
new file mode 100644
index 0000000..9354879
--- /dev/null
+++ b/saedit/file.h
@@ -0,0 +1,16 @@
+#ifndef _FILE_H_
+#define _FILE_H_
+
+gboolean
+show_unsaved_changes_dialog (void);
+
+const gchar *
+get_opened_file_name (void);
+
+void
+open_file (const gchar *filename);
+
+void
+save_file (const gchar *filename);
+
+#endif
diff --git a/saedit/glade.sh b/saedit/glade.sh
new file mode 100755
index 0000000..77f5b18
--- /dev/null
+++ b/saedit/glade.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+GLADE_CATALOG_SEARCH_PATH=glade GLADE_MODULE_SEARCH_PATH=glade glade $*
diff --git a/saedit/glade/saedit-catalog.xml b/saedit/glade/saedit-catalog.xml
new file mode 100644
index 0000000..4f304a7
--- /dev/null
+++ b/saedit/glade/saedit-catalog.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glade-catalog name="saedit" library="saedit" depends="gtk+">
+ <glade-widget-classes>
+ <glade-widget-class
+ name="TreeFolderView"
+ generic-name="treefolderview"
+ title="TreeFolderView"
+ />
+ <glade-widget-class
+ name="SpriteDrawingArea"
+ generic-name="spritedrawingarea"
+ title="SpriteDrawingArea"
+ />
+ <glade-widget-class
+ name="GtkSourceBuffer"
+ generic-name="sourcebuffer"
+ title="GtkSourceBuffer"
+ />
+ </glade-widget-classes>
+
+ <glade-widget-group name="saedit" title="SAEdit">
+ <glade-widget-class-ref name="TreeFolderView"/>
+ <glade-widget-class-ref name="SpriteDrawingArea"/>
+ <glade-widget-class-ref name="GtkSourceBuffer"/>
+ </glade-widget-group>
+</glade-catalog>
diff --git a/saedit/iface.ui b/saedit/iface.ui
new file mode 100644
index 0000000..dd1cab9
--- /dev/null
+++ b/saedit/iface.ui
@@ -0,0 +1,577 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <requires lib="gtksourceview" version="3.0"/>
+ <requires lib="saedit" version="0.0"/>
+ <object class="GtkAccelGroup" id="accelgroup1"/>
+ <object class="GtkFileFilter" id="filefilter-xml">
+ <patterns>
+ <pattern>*.xml</pattern>
+ </patterns>
+ </object>
+ <object class="GtkListStore" id="liststore-actions">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ <!-- column-name hp -->
+ <column type="gint"/>
+ <!-- column-name id -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkSourceBuffer" id="sourceview-buffer">
+ <signal name="modified-changed" handler="sourceview_buffer_modified_changed_cb" swapped="no"/>
+ </object>
+ <object class="GtkAdjustment" id="zoom-adjustment">
+ <property name="lower">1</property>
+ <property name="upper">10</property>
+ <property name="value">1</property>
+ <property name="step_increment">0.10000000000000001</property>
+ <property name="page_increment">0.5</property>
+ <signal name="value-changed" handler="zoom_adjustment_value_changed_cb" object="drawingarea-main" swapped="no"/>
+ </object>
+ <object class="GtkWindow" id="window-main">
+ <property name="can_focus">False</property>
+ <property name="icon">logo.svg</property>
+ <signal name="delete-event" handler="window_main_delete_event_cb" swapped="no"/>
+ <signal name="destroy" handler="gtk_main_quit" swapped="no"/>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkMenuBar" id="menubar3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="menuitem9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_File</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="accel_group">accelgroup1</property>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem-file-new">
+ <property name="label">gtk-new</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="new_file_activated_cb" swapped="no"/>
+ <accelerator key="n" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem-file-open">
+ <property name="label">gtk-open</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="open_file_activated_cb" swapped="no"/>
+ <accelerator key="o" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem-file-save">
+ <property name="label">gtk-save</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="save_file_activated_cb" swapped="no"/>
+ <accelerator key="s" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem-file-save-as">
+ <property name="label">gtk-save-as</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="save_file_as_activated_cb" swapped="no"/>
+ <accelerator key="s" signal="activate" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="&lt;separator&gt;">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem-choose-df">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Choose data folder...</property>
+ <signal name="activate" handler="choose_df_activated_cb" object="treefolderview-main" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem25">
+ <property name="label">gtk-quit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_View</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="menuitem-view-reset-to-center">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Reset to center</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="view_reset_to_center_activate_cb" swapped="no"/>
+ <accelerator key="r" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckMenuItem" id="menuitem-show-tile-grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Tile grid</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <signal name="toggled" handler="show_tile_grid_toggled_cb" swapped="no"/>
+ <accelerator key="g" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckMenuItem" id="menuitem-show-pixel-grid">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Pixel grid</property>
+ <property name="use_underline">True</property>
+ <signal name="toggled" handler="show_pixel_grid_toggled_cb" swapped="no"/>
+ <accelerator key="g" signal="activate" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Help</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem-about">
+ <property name="label">gtk-about</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="menuitem_about_activate_cb" object="about-dialog" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkPaned" id="paned2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="position">1</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow">
+ <property name="width_request">100</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="TreeFolderView" id="treefolderview-main">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="filter">filefilter-xml</property>
+ <property name="filename">/home/</property>
+ <signal name="file-activated" handler="tfview_file_activated_cb" object="sourceview-main" swapped="no"/>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeview-selection1"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkPaned" id="paned3">
+ <property name="width_request">100</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="orientation">vertical</property>
+ <property name="position">100</property>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="height_request">100</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="SpriteDrawingArea" id="drawingarea-main">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="events">GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScale" id="zoom-scale">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="orientation">vertical</property>
+ <property name="adjustment">zoom-adjustment</property>
+ <property name="inverted">True</property>
+ <property name="restrict_to_fill_level">False</property>
+ <property name="round_digits">1</property>
+ <property name="has_origin">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="box2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Action:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="cbox-actions">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">liststore-actions</property>
+ <property name="id_column">2</property>
+ <signal name="changed" handler="action_changed_cb" swapped="no"/>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext2">
+ <property name="xalign">1</property>
+ <property name="alignment">right</property>
+ </object>
+ <attributes>
+ <attribute name="text">1</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Direction: </property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBoxText" id="cboxtext-directions">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="button_sensitivity">on</property>
+ <signal name="changed" handler="direction_changed_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolbar" id="toolbar-animation">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkToolButton" id="button-parse-buffer">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Parse buffer</property>
+ <property name="stock_id">gtk-execute</property>
+ <signal name="clicked" handler="parse_buffer_clicked_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="&lt;separator&gt;1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="button-anim-first">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">First frame</property>
+ <property name="stock_id">gtk-goto-first</property>
+ <signal name="clicked" handler="first_frame_clicked_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="button-anim-prev">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Previous frame</property>
+ <property name="stock_id">gtk-go-back</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="button-anim-play">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Play/Pause</property>
+ <property name="use_underline">True</property>
+ <property name="icon_name">media-playback-start</property>
+ <signal name="clicked" handler="play_pause_clicked_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="button-anim-next">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Next frame</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-go-forward</property>
+ <signal name="clicked" handler="next_frame_clicked_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="button-anim-last">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Last frame</property>
+ <property name="use_underline">True</property>
+ <property name="stock_id">gtk-goto-last</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="width_request">100</property>
+ <property name="height_request">100</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkSourceView" id="sourceview-main">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="left_margin">2</property>
+ <property name="right_margin">2</property>
+ <property name="buffer">sourceview-buffer</property>
+ <property name="monospace">True</property>
+ <property name="show_line_numbers">True</property>
+ <property name="show_line_marks">True</property>
+ <property name="tab_width">4</property>
+ <property name="indent_width">4</property>
+ <property name="auto_indent">True</property>
+ <property name="insert_spaces_instead_of_tabs">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkAboutDialog" id="about-dialog">
+ <property name="can_focus">False</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="transient_for">window-main</property>
+ <property name="program_name">Sprite Animation Editor</property>
+ <property name="version">alpha2</property>
+ <property name="copyright" translatable="yes">Copyleft (É”) Vasily_Makarov</property>
+ <property name="comments" translatable="yes">Editor, parser and player for the TMW/Evol project animations. Greatly inspired by the project developers team.</property>
+ <property name="authors">Danil "Vasily Makarov" Sagunov &lt;vasily@evolonline.org&gt;</property>
+ <property name="logo">logo.svg</property>
+ <property name="license_type">gpl-3-0</property>
+ <signal name="response" handler="about_dialog_response_cb" swapped="no"/>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox">
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/saedit/imageset.c b/saedit/imageset.c
new file mode 100644
index 0000000..9ae2ea0
--- /dev/null
+++ b/saedit/imageset.c
@@ -0,0 +1,130 @@
+#include "imageset.h"
+#include <string.h>
+
+struct _Imageset {
+ gchar *name;
+ gint width, height;
+ gint offsetX, offsetY;
+
+ GdkPixbuf *pixbuf;
+};
+
+Imageset *
+imageset_new (
+ const XMLNode *node,
+ const gchar *cdf_filename
+) {
+ Imageset *imgset;
+ gchar *src, *filename;
+ GError *err = NULL;
+ gboolean fail = FALSE;
+
+ g_return_val_if_fail (g_strcmp0 (node->name, "imageset") == 0, NULL);
+
+ imgset = (Imageset *) g_new0 (Imageset, 1);
+
+ imgset->name = xml_node_get_attr_value (node, "name");
+ if (imgset->name == NULL) {
+ /* TODO: report error */
+ fail = TRUE;
+ }
+
+ src = xml_node_get_attr_value (node, "src");
+ if (src == NULL) {
+ /* TODO: report error */
+ fail = TRUE;
+ } else {
+ gchar *delim = strchr (src, '|');
+ if (delim != NULL)
+ *delim = 0;
+ /* TODO: process palettes? */
+ }
+
+ imgset->width = xml_node_get_int_attr_value (node, "width", -1);
+ imgset->height = xml_node_get_int_attr_value (node, "height", -1);
+ if (imgset->width <= 0 || imgset->height <= 0) {
+ /* TODO: report error */
+ fail = TRUE;
+ }
+
+ imgset->offsetX = xml_node_get_int_attr_value (node, "offsetX", 0);
+ imgset->offsetY = xml_node_get_int_attr_value (node, "offsetY", 0);
+
+ filename = g_strconcat (cdf_filename, "/", src, NULL);
+ imgset->pixbuf = gdk_pixbuf_new_from_file (filename, &err);
+
+ if (imgset->pixbuf == NULL) {
+ /* TODO handle err and report error */
+ fail = TRUE;
+ }
+
+ if (fail) {
+ imageset_free (imgset);
+ return NULL;
+ }
+
+ return imgset;
+}
+
+gint
+imageset_compare_by_name (
+ const Imageset *first,
+ const Imageset *second
+) {
+ return g_strcmp0 (first->name, second->name);
+}
+
+GdkPixbuf *
+imageset_get_sprite_by_index (
+ const Imageset *imageset,
+ gint index
+) {
+ gint w = gdk_pixbuf_get_width (imageset->pixbuf);
+ w /= imageset->width;
+
+ g_return_val_if_fail (index >= 0, NULL);
+
+ return gdk_pixbuf_new_subpixbuf (
+ imageset->pixbuf,
+ (index % w) * imageset->width,
+ (index / w) * imageset->height,
+ imageset->width,
+ imageset->height
+ );
+}
+
+void
+imageset_free (Imageset *imgset) {
+ g_free (imgset->name);
+ if (imgset->pixbuf != NULL)
+ g_object_unref (imgset->pixbuf);
+ g_free (imgset);
+}
+
+gboolean
+imageset_name_equals (
+ const Imageset *imageset,
+ const gchar *name
+) {
+ return g_strcmp0 (imageset->name, name) == 0;
+}
+
+void
+imageset_get_offset (
+ const Imageset *imageset,
+ gint *offsetX,
+ gint *offsetY
+) {
+ *offsetX = imageset->offsetX;
+ *offsetY = imageset->offsetY;
+}
+
+void
+imageset_get_size (
+ const Imageset *imageset,
+ gint *width,
+ gint *height
+) {
+ *width = imageset->width;
+ *height = imageset->height;
+}
diff --git a/saedit/imageset.h b/saedit/imageset.h
new file mode 100644
index 0000000..3496d65
--- /dev/null
+++ b/saedit/imageset.h
@@ -0,0 +1,50 @@
+#ifndef IMAGESET_H
+#define IMAGESET_H
+
+#include <gdk/gdk.h>
+#include "common.h"
+#include "xml.h"
+
+typedef struct _Imageset Imageset;
+
+Imageset *
+imageset_new (
+ const XMLNode *node,
+ const gchar *cdf_filename
+);
+
+void
+imageset_free (Imageset *imageset);
+
+gint
+imageset_compare_by_name (
+ const Imageset *first,
+ const Imageset *second
+);
+
+GdkPixbuf *
+imageset_get_sprite_by_index (
+ const Imageset *imageset,
+ gint index
+);
+
+gboolean
+imageset_name_equals (
+ const Imageset *imageset,
+ const gchar *name
+);
+
+void
+imageset_get_offset (
+ const Imageset *imageset,
+ gint *offsetX,
+ gint *offsetY
+);
+
+void
+imageset_get_size (
+ const Imageset *imageset,
+ gint *width,
+ gint *height
+);
+#endif
diff --git a/saedit/interactor.c b/saedit/interactor.c
new file mode 100644
index 0000000..85e55fa
--- /dev/null
+++ b/saedit/interactor.c
@@ -0,0 +1,439 @@
+#include <glib.h>
+#include <stdlib.h>
+
+#include "interactor.h"
+#include "imageset.h"
+#include "action.h"
+#include "animation.h"
+#include "errors.h"
+
+struct _Interactor {
+ const SpriteContext *context;
+ const Action *action;
+ const Animation *animation;
+ gchar *direction;
+ gboolean rand_checked;
+ GList *element;
+ gint delay;
+
+ guint loop_tag;
+ guint tick_length;
+
+ InteractionUpdatedFunc updated_cb;
+
+ GList *repeaters;
+};
+
+static gboolean
+interactor_updated_func (Interactor *interactor) {
+ g_return_val_if_fail (interactor->updated_cb != NULL, FALSE);
+
+ interactor->updated_cb (interactor);
+ return FALSE;
+}
+
+static void
+interactor_updated (Interactor *interactor) {
+ if (interactor->updated_cb == NULL)
+ return;
+
+ g_main_context_invoke (
+ NULL,
+ (GSourceFunc) interactor_updated_func,
+ interactor
+ );
+}
+
+static AnimElement *
+interactor_get_element (const Interactor *interactor) {
+ if (interactor->element == NULL)
+ return NULL;
+ return (AnimElement *) interactor->element->data;
+}
+
+Interactor *
+interactor_new (
+ const SpriteContext *context
+) {
+ Interactor *interactor = g_new0 (Interactor, 1);
+
+ interactor->context = context;
+ interactor->repeaters = NULL;
+ return interactor;
+}
+
+gboolean
+interactor_reset_animation (
+ Interactor *interactor
+) {
+ GList *l;
+
+ for (l = interactor->repeaters; l != NULL; l = l->next)
+ interactor_reset_animation ((Interactor *) l->data);
+
+ if (interactor->action == NULL)
+ return FALSE;
+
+ interactor->animation = action_get_animation (
+ interactor->action,
+ interactor->direction
+ );
+
+ g_return_val_if_fail (interactor->animation != NULL, FALSE);
+
+ interactor->element = interactor->animation->elements;
+ interactor->delay = 0;
+ interactor->rand_checked = TRUE;
+ interactor_updated (interactor);
+ interactor_play (interactor, 0);
+
+ return TRUE;
+}
+
+gboolean
+interactor_set_action (
+ Interactor *interactor,
+ gint hp,
+ const gchar *name
+) {
+ Action *action;
+ GList *l;
+
+ for (l = interactor->repeaters; l != NULL; l = l->next)
+ interactor_set_action ((Interactor *) l->data, hp, name);
+
+ if (
+ interactor->action != NULL &&
+ action_hp_and_name_equals (interactor->action, hp, name)
+ )
+ return FALSE;
+
+ action = sprite_context_get_action (
+ interactor->context, hp, name
+ );
+
+ if (action == NULL)
+ return FALSE;
+
+ interactor->action = action;
+ return interactor_reset_animation (interactor);
+}
+
+gboolean
+interactor_set_direction (
+ Interactor *interactor,
+ const gchar *direction
+) {
+ GList *l;
+
+ for (l = interactor->repeaters; l != NULL; l = l->next)
+ interactor_set_direction ((Interactor *) l->data, direction);
+
+ if (g_strcmp0 (interactor->direction, direction) != 0) {
+ if (interactor->direction != NULL)
+ g_free (interactor->direction);
+
+ interactor->direction = g_strdup (direction);
+ return interactor_reset_animation (interactor);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+_animation_element_rand_check (
+ const AnimElement *element
+) {
+ if (element->rand == 100) return TRUE;
+ if (element->rand == 0) return FALSE;
+ return rand() % 100 < element->rand;
+}
+
+gboolean
+interactor_play (
+ Interactor *interactor,
+ gint time
+) {
+ gboolean updated = FALSE;
+ GList *l;
+
+ for (l = interactor->repeaters; l != NULL; l = l->next)
+ interactor_play ((Interactor *) l->data, time);
+
+ g_return_val_if_fail (time >= 0, FALSE);
+
+ if (interactor->action == NULL)
+ return FALSE;
+ if (interactor->animation == NULL)
+ return FALSE;
+ g_return_val_if_fail (interactor->element != NULL, FALSE);
+
+ interactor->delay += time;
+
+ while (TRUE) {
+ AnimElement *element = interactor_get_element (interactor);
+ gint e_delay = element->delay;
+
+ if ( interactor->rand_checked ||
+ _animation_element_rand_check (element)
+ ) {
+ interactor->rand_checked = TRUE;
+
+ if (interactor->delay < e_delay)
+ break;
+
+ interactor->delay -= e_delay;
+ interactor->rand_checked = FALSE;
+ updated = TRUE;
+
+ if (element->type == ELEMENT_END) {
+ interactor_reset_animation (interactor);
+ return FALSE;
+ } else
+
+ if (element->type == ELEMENT_FRAME) {
+ if (e_delay == 0)
+ break;
+ } else
+
+ if (element->type == ELEMENT_PAUSE) {
+ if (e_delay == 0)
+ break;
+ } else
+
+ if (element->type == ELEMENT_JUMP) {
+ gint delay = interactor->delay;
+
+ gboolean found = interactor_set_action (
+ interactor,
+ interactor->action->hp,
+ element->str
+ );
+
+ if (!found) {
+ /* TODO: report about this */
+ return FALSE;
+ }
+
+ return interactor_play (
+ interactor,
+ delay
+ );
+ } else
+
+ if (element->type == ELEMENT_LABEL) {
+
+ } else
+
+ if (element->type == ELEMENT_GOTO) {
+ GList *nelem =
+ interactor->animation->elements;
+
+ while (nelem != NULL) {
+ AnimElement *current =
+ (AnimElement *) nelem->data;
+ if (current->type == ELEMENT_LABEL) {
+ if (g_strcmp0 (
+ current->str,
+ element->str) == 0
+ )
+ break;
+ }
+
+ nelem = g_list_next (nelem);
+ }
+
+ if (nelem != NULL) {
+ interactor->element = nelem;
+ continue;
+ } else {
+ post_error ("Playback", "Specified goto label not found");
+ return FALSE;
+ }
+ }
+ }
+
+ interactor->element = g_list_next (interactor->element);
+ if (interactor->element == NULL)
+ interactor->element = interactor->animation->elements;
+ }
+
+ if (updated)
+ interactor_updated (interactor);
+
+ return TRUE;
+}
+
+const GdkPixbuf *
+interactor_get_sprite (const Interactor *interactor) {
+ if (interactor->element == NULL)
+ return NULL;
+ return interactor_get_element (interactor)->sprite;
+}
+
+void
+interactor_get_offset (
+ const Interactor *interactor,
+ gint *offsetX,
+ gint *offsetY
+) {
+ AnimElement *element = interactor_get_element (interactor);
+ if (element == NULL)
+ return;
+
+ *offsetX = element->offsetX;
+ *offsetY = element->offsetY;
+}
+
+gboolean
+interactor_loop_tick (Interactor *interactor) {
+ gboolean result = interactor_play (
+ interactor,
+ interactor->tick_length
+ );
+
+ if (result == FALSE) {
+ interactor->loop_tag = 0;
+ interactor_updated (interactor);
+ }
+
+ return result;
+}
+
+void
+interactor_loop_start (
+ Interactor *interactor,
+ const guint interval,
+ const guint tick_length
+) {
+ if (interactor->loop_tag != 0)
+ return;
+
+ interactor->tick_length = tick_length;
+ interactor->loop_tag = g_timeout_add (
+ interval,
+ (GSourceFunc) interactor_loop_tick,
+ interactor
+ );
+
+ interactor_updated (interactor);
+}
+
+gboolean
+interactor_loop_stop (Interactor *interactor) {
+ if (interactor->loop_tag == 0)
+ return FALSE;
+
+ g_source_remove (interactor->loop_tag);
+ interactor->loop_tag = 0;
+
+ interactor_updated (interactor);
+
+ return TRUE;
+}
+
+gboolean
+interactor_loop_running (
+ const Interactor *interactor
+) {
+ if (interactor == NULL)
+ return FALSE;
+ return interactor->loop_tag != 0;
+}
+
+void
+interactor_free (Interactor *interactor) {
+ interactor_loop_stop (interactor);
+ g_list_free (interactor->repeaters);
+ g_free (interactor);
+}
+
+void
+interactor_free_with_repeaters (Interactor *interactor) {
+ interactor_loop_stop (interactor);
+
+ g_list_free_full (
+ interactor->repeaters,
+ (GDestroyNotify) interactor_free_with_repeaters
+ );
+
+ g_free (interactor);
+}
+
+void
+interactor_set_updated_callback (
+ Interactor *interactor,
+ InteractionUpdatedFunc callback
+) {
+ interactor->updated_cb = callback;
+}
+
+gint
+interactor_get_line_no (
+ Interactor *interactor
+) {
+ AnimElement *element = interactor_get_element (interactor);
+
+ if (element == NULL)
+ return -1;
+
+ return element->line_no;
+}
+
+const gchar *
+interactor_get_animation_direction (
+ const Interactor *interactor
+) {
+ if (interactor->animation == NULL)
+ return NULL;
+ return interactor->animation->direction;
+}
+
+const gchar *
+interactor_get_direction (
+ const Interactor *interactor
+) {
+ return interactor->direction;
+}
+
+gboolean
+interactor_get_action_hp_and_name (
+ const Interactor *interactor,
+ gint *hp,
+ gchar **name
+) {
+ if (interactor->action == NULL)
+ return FALSE;
+
+ action_get_hp_and_name (interactor->action, hp, name);
+ return TRUE;
+}
+
+void
+interactor_skip_current_frame (
+ Interactor *interactor
+) {
+ AnimElement *element;
+
+ interactor_loop_stop (interactor);
+ element = interactor_get_element (interactor);
+
+ if (element != NULL) {
+ g_return_if_fail (interactor->delay <= element->delay);
+ interactor_play (
+ interactor,
+ element->delay - interactor->delay
+ );
+ }
+}
+
+void
+interactor_add_repeater (
+ Interactor *interactor,
+ Interactor *repeater
+) {
+ interactor->repeaters = g_list_append (
+ interactor->repeaters,
+ repeater
+ );
+}
diff --git a/saedit/interactor.h b/saedit/interactor.h
new file mode 100644
index 0000000..5d0ddfc
--- /dev/null
+++ b/saedit/interactor.h
@@ -0,0 +1,110 @@
+#ifndef _INTERACTOR_H_
+#define _INTERACTOR_H_
+
+#include "context.h"
+
+typedef struct _Interactor Interactor;
+
+Interactor *
+interactor_new (
+ const SpriteContext *context
+);
+
+gboolean
+interactor_set_action (
+ Interactor *interactor,
+ gint hp,
+ const gchar *name
+);
+
+gboolean
+interactor_reset_animation (
+ Interactor *interactor
+);
+
+gboolean
+interactor_set_direction (
+ Interactor *interactor,
+ const gchar *direction
+);
+
+gboolean
+interactor_play (
+ Interactor *interactor,
+ gint time
+);
+
+const GdkPixbuf *
+interactor_get_sprite (const Interactor *interactor);
+
+void
+interactor_loop_start (
+ Interactor *interactor,
+ const guint interval,
+ const guint tick_length
+);
+
+gboolean
+interactor_loop_stop (Interactor *interactor);
+
+void
+interactor_free (Interactor *interactor);
+
+void
+interactor_free_with_repeaters (Interactor *interactor);
+
+typedef void
+(*InteractionUpdatedFunc) (Interactor *interactor);
+
+void
+interactor_set_updated_callback (
+ Interactor *interactor,
+ InteractionUpdatedFunc callback
+);
+
+void
+interactor_get_offset (
+ const Interactor *interactor,
+ gint *offsetX,
+ gint *offsetY
+);
+
+gint
+interactor_get_line_no (
+ Interactor *interactor
+);
+
+const gchar *
+interactor_get_animation_direction (
+ const Interactor *interactor
+);
+
+const gchar *
+interactor_get_direction (
+ const Interactor *interactor
+);
+
+gboolean
+interactor_get_action_hp_and_name (
+ const Interactor *interactor,
+ gint *hp,
+ gchar **name
+);
+
+gboolean
+interactor_loop_running (
+ const Interactor *interactor
+);
+
+void
+interactor_skip_current_frame (
+ Interactor *interactor
+);
+
+void
+interactor_add_repeater (
+ Interactor *interactor,
+ Interactor *repeater
+);
+
+#endif
diff --git a/saedit/logo.svg b/saedit/logo.svg
new file mode 100644
index 0000000..a68864a
--- /dev/null
+++ b/saedit/logo.svg
@@ -0,0 +1,657 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="256"
+ height="256"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ sodipodi:docname="logo.svg">
+ <defs
+ id="defs4">
+ <linearGradient
+ y2="445.16745"
+ x2="726.91016"
+ y1="388.45044"
+ x1="726.91016"
+ gradientTransform="matrix(0.2400412,0,0,1.0091871,-59.607089,50.7501)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2980"
+ xlink:href="#linearGradient3164"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="445.16745"
+ x2="726.91016"
+ y1="388.45044"
+ x1="726.91016"
+ gradientTransform="matrix(1.2809327,0,0,1.0091871,-753.11664,-9.2499)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2978"
+ xlink:href="#linearGradient3164"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,22.460937,-1381.7662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2976"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientTransform="translate(22.460937,0.5)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2974"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,0,-1382.2662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2972"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2970"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,0,-1382.2662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2968"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2966"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,0,-1382.2662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2964"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2962"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,0,-1382.2662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2960"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2958"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,0,-1382.2662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2956"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2954"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,0,-1382.2662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2952"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2950"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,0,-1382.2662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2948"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2946"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="445.16745"
+ x2="726.91016"
+ y1="388.45044"
+ x1="726.91016"
+ gradientTransform="matrix(0.3012701,0,0,1.0091871,209.77505,50.7501)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2944"
+ xlink:href="#linearGradient3164"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="445.16745"
+ x2="726.91016"
+ y1="388.45044"
+ x1="726.91016"
+ gradientTransform="matrix(0.1680072,0,0,1.0091871,508.68297,50.7501)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2942"
+ xlink:href="#linearGradient3164"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="445.16745"
+ x2="726.91016"
+ y1="388.45044"
+ x1="726.91016"
+ gradientTransform="matrix(0.2400412,0,0,1.0091871,-59.607089,50.7501)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient25734"
+ xlink:href="#linearGradient3164"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="445.16745"
+ x2="726.91016"
+ y1="388.45044"
+ x1="726.91016"
+ gradientTransform="matrix(1.2809327,0,0,1.0091871,-753.11664,-9.2499)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient25732"
+ xlink:href="#linearGradient3164"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,22.460937,-1381.7662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient25730"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientTransform="translate(22.460937,0.5)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient25728"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,0,-1382.2662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient25726"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient25724"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,0,-1382.2662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient25722"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient25720"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,0,-1382.2662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient25718"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient25716"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,0,-1382.2662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient25714"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient25712"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,0,-1382.2662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient25710"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient25708"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,0,-1382.2662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient25706"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient25704"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <radialGradient
+ r="7.1330748"
+ fy="469.36499"
+ fx="506.24673"
+ cy="471.40497"
+ cx="508.29752"
+ gradientTransform="matrix(1,0,0,3.9254336,0,-1382.2662)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient25702"
+ xlink:href="#linearGradient4850"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="502.34891"
+ x2="480.07483"
+ y1="443.00705"
+ x1="480.07483"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient25700"
+ xlink:href="#linearGradient4800"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="445.16745"
+ x2="726.91016"
+ y1="388.45044"
+ x1="726.91016"
+ gradientTransform="matrix(0.3012701,0,0,1.0091871,209.77505,50.7501)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient25698"
+ xlink:href="#linearGradient3164"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="445.16745"
+ x2="726.91016"
+ y1="388.45044"
+ x1="726.91016"
+ gradientTransform="matrix(0.1680072,0,0,1.0091871,508.68297,50.7501)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient25696"
+ xlink:href="#linearGradient3164"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="445.16745"
+ x2="726.91016"
+ y1="388.45044"
+ x1="726.91016"
+ gradientTransform="matrix(0.2400412,0,0,1.0091871,-78.389613,-49.2499)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient24956"
+ xlink:href="#linearGradient3164"
+ inkscape:collect="always" />
+ <clipPath
+ id="clipPath24952"
+ clipPathUnits="userSpaceOnUse">
+ <path
+ sodipodi:nodetypes="ccscc"
+ id="path24954"
+ d="M 75.200062,372.5 L 145.76888,400 C 152.63003,400 158.20638,387.68 158.20638,372.5 C 158.20638,357.32 152.63003,345 145.76888,345 L 75.200062,372.5 z"
+ style="opacity:1;fill:url(#linearGradient24956);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:36, 12;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <linearGradient
+ id="linearGradient3164">
+ <stop
+ style="stop-color:#383838;stop-opacity:1"
+ offset="0"
+ id="stop3166" />
+ <stop
+ id="stop3172"
+ offset="0.05754738"
+ style="stop-color:#3b3b3b;stop-opacity:1" />
+ <stop
+ style="stop-color:#626463;stop-opacity:1"
+ offset="0.07978155"
+ id="stop3174" />
+ <stop
+ id="stop3176"
+ offset="0.12359595"
+ style="stop-color:#bfbfbf;stop-opacity:1" />
+ <stop
+ style="stop-color:#808080;stop-opacity:1"
+ offset="0.18899058"
+ id="stop3178" />
+ <stop
+ id="stop3180"
+ offset="0.25765494"
+ style="stop-color:#e0e0e0;stop-opacity:1" />
+ <stop
+ style="stop-color:#ececec;stop-opacity:1"
+ offset="0.37405738"
+ id="stop3182" />
+ <stop
+ id="stop3184"
+ offset="0.52577293"
+ style="stop-color:#828282;stop-opacity:1" />
+ <stop
+ style="stop-color:#818181;stop-opacity:1"
+ offset="0.57285708"
+ id="stop3186" />
+ <stop
+ id="stop3188"
+ offset="0.67356479"
+ style="stop-color:#acacac;stop-opacity:1" />
+ <stop
+ style="stop-color:#bcbcbc;stop-opacity:1"
+ offset="0.73765153"
+ id="stop3190" />
+ <stop
+ id="stop3192"
+ offset="0.86451715"
+ style="stop-color:#505050;stop-opacity:1" />
+ <stop
+ style="stop-color:#333333;stop-opacity:1"
+ offset="0.90767759"
+ id="stop3194" />
+ <stop
+ id="stop3196"
+ offset="0.96260905"
+ style="stop-color:#7a7a7c;stop-opacity:1" />
+ <stop
+ style="stop-color:#c9c6c1;stop-opacity:1"
+ offset="1"
+ id="stop3168" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4800">
+ <stop
+ id="stop4802"
+ offset="0"
+ style="stop-color:#383838;stop-opacity:1" />
+ <stop
+ style="stop-color:#3b3b3b;stop-opacity:1"
+ offset="0.05754738"
+ id="stop4804" />
+ <stop
+ id="stop4806"
+ offset="0.07978155"
+ style="stop-color:#626463;stop-opacity:1" />
+ <stop
+ style="stop-color:#bfbfbf;stop-opacity:1"
+ offset="0.12359595"
+ id="stop4808" />
+ <stop
+ id="stop4810"
+ offset="0.18899058"
+ style="stop-color:#808080;stop-opacity:1" />
+ <stop
+ style="stop-color:#e0e0e0;stop-opacity:1"
+ offset="0.25765494"
+ id="stop4812" />
+ <stop
+ id="stop4814"
+ offset="0.37405738"
+ style="stop-color:#ececec;stop-opacity:1" />
+ <stop
+ style="stop-color:#828282;stop-opacity:1"
+ offset="0.52577293"
+ id="stop4816" />
+ <stop
+ id="stop4818"
+ offset="0.57285708"
+ style="stop-color:#818181;stop-opacity:1" />
+ <stop
+ style="stop-color:#acacac;stop-opacity:1"
+ offset="0.67356479"
+ id="stop4820" />
+ <stop
+ id="stop4822"
+ offset="0.73765153"
+ style="stop-color:#bcbcbc;stop-opacity:1" />
+ <stop
+ style="stop-color:#505050;stop-opacity:1"
+ offset="0.86451715"
+ id="stop4824" />
+ <stop
+ id="stop4826"
+ offset="0.90767759"
+ style="stop-color:#333333;stop-opacity:1" />
+ <stop
+ style="stop-color:#7a7a7c;stop-opacity:1"
+ offset="0.96260905"
+ id="stop4828" />
+ <stop
+ id="stop4830"
+ offset="1"
+ style="stop-color:#c9c6c1;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4850">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4852" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4854" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.4142136"
+ inkscape:cx="33.799066"
+ inkscape:cy="114.6248"
+ inkscape:document-units="px"
+ inkscape:current-layer="g3107"
+ showgrid="false"
+ inkscape:window-width="1366"
+ inkscape:window-height="704"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-796.36209)">
+ <g
+ transform="translate(0,796.36209)"
+ style="font-size:40px;font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Charter"
+ id="text3080" />
+ <g
+ id="g3107"
+ transform="matrix(5.7032977,0,0,5.7032977,-463.19349,-3989.5514)"
+ style="fill:#800000">
+ <g
+ transform="translate(-0.55621842,0.00758253)"
+ id="text3086"
+ style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#800000;fill-opacity:1;stroke:none;font-family:Cantarell;-inkscape-font-specification:Sans">
+ <path
+ inkscape:connector-curvature="0"
+ id="path3105"
+ style="font-family:Sans;-inkscape-font-specification:Sans;fill:#800000"
+ d="m 110.87616,841.71545 v 3.39844 c -1.01564,-0.52081 -2.07033,-0.91144 -3.16406,-1.17187 -1.09377,-0.2604 -2.22658,-0.39061 -3.39844,-0.39063 -1.78386,2e-5 -3.12501,0.27346 -4.02344,0.82031 -0.885421,0.5469 -1.328129,1.36721 -1.328123,2.46094 -6e-6,0.83335 0.319004,1.4909 0.957031,1.97266 0.638012,0.46876 1.920562,0.91798 3.847652,1.34765 l 1.23047,0.27344 c 2.55207,0.54689 4.36197,1.32163 5.42969,2.32422 1.08071,0.98959 1.62108,2.37631 1.62109,4.16016 -1e-5,2.03125 -0.80731,3.63932 -2.42187,4.82422 -1.60158,1.18489 -3.80861,1.77734 -6.62109,1.77734 -1.17189,0 -2.39584,-0.11719 -3.671879,-0.35156 -1.263026,-0.22136 -2.59766,-0.5599 -4.003906,-1.01563 l -1.611767,-1.9132 c 1.328121,0.69011 1.846328,-1.14472 5.537548,-0.23524 1.289054,0.33855 2.565094,0.50782 3.828124,0.50782 1.6927,0 2.99478,-0.28646 3.90625,-0.85938 0.91144,-0.58593 1.36717,-1.40624 1.36719,-2.46094 -2e-5,-0.97655 -0.33205,-1.72525 -0.9961,-2.24609 -0.65105,-0.52082 -2.08985,-1.02213 -4.3164,-1.50391 l -1.25,-0.29296 c -2.22657,-0.46874 -3.834642,-1.18489 -4.82422,-2.14844 -0.989586,-0.97655 -1.484378,-2.31119 -1.484375,-4.00391 -3e-6,-2.05727 0.729163,-3.64581 2.1875,-4.76562 1.458327,-1.11977 3.528635,-1.67967 6.210935,-1.67969 1.32812,2e-5 2.57811,0.0977 3.75,0.29297 1.17186,0.19533 2.25259,0.4883 3.24219,0.8789"
+ sodipodi:nodetypes="ccccccccccccsccccsccccccccccc" />
+ </g>
+ <g
+ id="text3090"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:40px;line-height:125%;font-family:Cantarell;-inkscape-font-specification:Sans;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#800000;fill-opacity:1;stroke:none">
+ <path
+ id="path3102"
+ style="font-family:Sans;-inkscape-font-specification:Sans;fill:#800000"
+ d="m 97.149544,871.06958 c -2.903657,10e-6 -4.915374,0.33204 -6.035156,0.99609 -1.119799,0.66408 -1.679694,1.79689 -1.679688,3.39844 -6e-6,1.27605 0.41666,2.29167 1.25,3.04688 0.846346,0.74219 1.992178,1.11328 3.4375,1.11328 1.992175,0 3.587225,-0.70312 4.785156,-2.10938 1.210924,-1.41926 1.816394,-3.30077 1.816404,-5.64453 v -0.80078 h -3.574216 m 7.167966,-1.48437 v 12.48046 h -3.59375 v -3.32031 c -0.820326,1.32813 -1.842461,2.3112 -3.066404,2.94922 -1.223971,0.625 -2.721365,0.9375 -4.492187,0.9375 -2.239591,0 -4.023443,-0.625 -5.351563,-1.875 -1.315107,-1.26302 -1.972658,-2.94921 -1.972656,-5.05859 -2e-6,-2.46093 0.820309,-4.3164 2.460938,-5.56641 1.653639,-1.24999 4.114574,-1.87499 7.382812,-1.875 h 5.03906 v -0.35156 c -1e-5,-1.65363 -0.54689,-2.92967 -1.640622,-3.82813 -1.080744,-0.91144 -2.60418,-1.36717 -4.570313,-1.36718 -1.25001,1e-5 -2.467457,0.14975 -3.652344,0.44921 -1.184902,0.2995 -2.324224,0.74872 -3.417968,1.34766 v -3.32031 c 1.315098,-0.50779 2.591139,-0.8854 3.828125,-1.13281 1.23697,-0.2604 2.441395,-0.39061 3.613281,-0.39063 3.180647,-0.20787 5.527331,0.82033 7.089841,2.46094 1.56248,1.64064 2.34373,4.12762 2.34375,7.46094"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccscccccccccsccccccccccccccc" />
+ </g>
+ <g
+ id="text3094"
+ style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;fill:#800000;fill-opacity:1;stroke:none;font-family:Cantarell;-inkscape-font-specification:Sans"
+ transform="translate(0,796.36209)">
+ <path
+ id="path3099"
+ style="fill:#800000;font-family:Sans;-inkscape-font-specification:Sans"
+ d="m 121.47542,73.044228 0,1.757812 -16.52344,0 c 0.15624,2.473966 0.89843,4.361985 2.22656,5.664063 1.34114,1.289065 3.20312,1.933596 5.58594,1.933593 1.38019,3e-6 2.71483,-0.169268 4.00391,-0.507812 1.30206,-0.338538 2.59112,-0.84635 3.86718,-1.523438 l 0,3.398438 c -1.28908,0.546875 -2.61069,0.963542 -3.96484,1.25 -1.35418,0.286458 -2.72788,0.429687 -4.12109,0.429687 -3.4896,0 -6.25652,-1.015624 -8.30078,-3.046875 -2.03126,-2.031245 -3.04688,-4.778638 -3.04688,-8.242187 0,-3.580715 0.96354,-6.419254 2.89063,-8.515625 1.94009,-2.109354 4.55077,-3.16404 7.83203,-3.164063 2.94269,2.3e-5 5.26691,0.950543 6.97265,2.851563 1.71873,1.888038 2.57811,4.45965 2.57813,7.714844 m -3.59375,-1.054688 c -0.0261,-1.966131 -0.57945,-3.53514 -1.66016,-4.707031 -1.06772,-1.171856 -2.48699,-1.757793 -4.25781,-1.757813 -2.00522,2e-5 -3.61329,0.566425 -4.82422,1.699219 -1.19792,1.132829 -1.88803,2.72788 -2.07031,4.785156 l 12.8125,-0.01953"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/saedit/main.c b/saedit/main.c
new file mode 100644
index 0000000..103e0c4
--- /dev/null
+++ b/saedit/main.c
@@ -0,0 +1,235 @@
+#include <gtksourceview/gtksource.h>
+#include "xml.h"
+#include "config.h"
+#include "spritedrawingarea.h"
+#include "action.h"
+#include "errors.h"
+#include "file.h"
+#include "buffer.h"
+#include "xmlsetup.h"
+#include "drawfuncs.h"
+
+#include "main.h"
+#include <string.h>
+
+void
+intr_updated (Interactor *interactor) {
+ gtk_widget_queue_draw (d_area);
+
+ gtk_tool_button_set_icon_name (
+ GTK_TOOL_BUTTON (tbtn_play),
+ interactor_loop_running (interactor) ?
+ "media-playback-pause" :
+ "media-playback-start"
+ );
+
+ if (interactor != NULL) {
+ gboolean result;
+ gint hp;
+ gchar *id, *name;
+
+ buffer_mark_line (
+ interactor_get_line_no (interactor) - 1
+ );
+
+ result = interactor_get_action_hp_and_name (
+ interactor,
+ &hp, &name
+ );
+
+ if (!result)
+ return;
+
+ id = get_action_id (hp, name);
+ gtk_combo_box_set_active_id (cb_actions, id);
+ g_free (id);
+
+ gtk_combo_box_set_active_id (
+ GTK_COMBO_BOX (cb_directions),
+ interactor_get_animation_direction (interactor)
+ );
+ } else
+ buffer_mark_line (-1);
+}
+
+void
+release_context () {
+ xml_setup_clear ();
+
+ if (interactor != NULL) {
+ interactor_free (interactor);
+ interactor = NULL;
+ intr_updated (interactor);
+ }
+
+ gtk_combo_box_text_remove_all (cb_directions);
+ gtk_list_store_clear (store_actions);
+
+ if (context != NULL) {
+ sprite_context_free (context);
+ context = NULL;
+ }
+}
+
+void
+update_window_title () {
+ gchar *title, *data_folder, *file_name;
+ const gchar *opened_file_name;
+
+ gboolean modified = buffer_get_modified ();
+ data_folder = config_keys_get_data_folder_path ();
+ opened_file_name = get_opened_file_name ();
+
+ if (
+ opened_file_name != NULL &&
+ g_str_has_prefix (opened_file_name, data_folder)
+ ) {
+ file_name = g_strconcat (
+ "<Data Folder>",
+ opened_file_name + strlen (data_folder),
+ NULL
+ );
+ } else {
+ file_name = g_strdup (opened_file_name);
+ }
+
+ if (file_name == NULL) {
+ file_name = g_strdup ("New animation");
+ }
+
+ title = g_strconcat (
+ modified ? "*" : "",
+ file_name,
+ NULL
+ );
+
+ gtk_window_set_title (GTK_WINDOW (main_window), title);
+ g_free (title);
+ g_free (file_name);
+}
+
+void
+setup_source_view (GtkSourceView *source_view) {
+ GtkSourceLanguageManager *langman;
+ GtkSourceMarkAttributes *attrs;
+
+ langman = gtk_source_language_manager_get_default();
+
+ gtk_source_buffer_set_language (
+ GTK_SOURCE_BUFFER (
+ gtk_text_view_get_buffer (
+ GTK_TEXT_VIEW (source_view)
+ )
+ ),
+ gtk_source_language_manager_get_language (langman, "xml")
+ );
+
+ attrs = gtk_source_mark_attributes_new ();
+ gtk_source_mark_attributes_set_icon_name (attrs, "media-record");
+ gtk_source_view_set_mark_attributes (
+ source_view, "active-line", attrs, 0
+ );
+
+ gtk_widget_show_all (GTK_WIDGET (source_view));
+}
+
+GtkWidget *
+gtk_builder_get_widget (
+ GtkBuilder *builder,
+ const gchar *name
+) {
+ return GTK_WIDGET (
+ gtk_builder_get_object (builder, name)
+ );
+}
+
+gboolean
+window_main_delete_event_cb (
+ GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data
+) {
+ return show_unsaved_changes_dialog ();
+}
+
+int
+main (int argc, char *argv[]) {
+ GtkBuilder *builder;
+ gchar *path;
+
+ gtk_init (&argc, &argv);
+
+ builder = gtk_builder_new ();
+ gtk_builder_add_from_file (builder, "iface.ui", NULL);
+ gtk_builder_connect_signals (builder, NULL);
+
+ main_window = gtk_builder_get_widget (builder, "window-main");
+ tf_view = gtk_builder_get_widget (builder, "treefolderview-main");
+ d_area = gtk_builder_get_widget (builder, "drawingarea-main");
+ source_view = gtk_builder_get_widget (builder, "sourceview-main");
+ tbtn_play = gtk_builder_get_widget (builder, "button-anim-play");
+
+ cb_actions = GTK_COMBO_BOX (
+ gtk_builder_get_widget (builder, "cbox-actions")
+ );
+ cb_directions = GTK_COMBO_BOX_TEXT (
+ gtk_builder_get_widget (builder, "cboxtext-directions")
+ );
+
+ store_actions = GTK_LIST_STORE (
+ gtk_builder_get_object (builder, "liststore-actions")
+ );
+
+ zoom_adj = GTK_ADJUSTMENT (
+ gtk_builder_get_object (builder, "zoom-adjustment")
+ );
+
+ g_object_unref (builder);
+
+ setup_source_view (GTK_SOURCE_VIEW (source_view));
+
+ update_window_title ();
+
+ path = config_keys_get_data_folder_path ();
+ tree_folder_view_set_filename (
+ TREE_FOLDER_VIEW (tf_view),
+ path
+ );
+ g_free (path);
+
+ sprite_drawing_area_add_layer (
+ SPRITE_DRAWING_AREA (d_area),
+ sda_layer_new (interactor_sprite_layer_draw_func, &interactor)
+ );
+
+ tile_grid_layer = sda_layer_new (
+ tile_grid_layer_draw_func,
+ NULL
+ );
+
+ sda_layer_set_z_index (tile_grid_layer, 512);
+
+ sprite_drawing_area_add_layer (
+ SPRITE_DRAWING_AREA (d_area),
+ tile_grid_layer
+ );
+
+ pixel_grid_layer = sda_layer_new (
+ pixel_grid_layer_draw_func,
+ NULL
+ );
+
+ sda_layer_set_z_index (pixel_grid_layer, 512 + 1);
+ sda_layer_set_visible (pixel_grid_layer, FALSE);
+
+ sprite_drawing_area_add_layer (
+ SPRITE_DRAWING_AREA (d_area),
+ pixel_grid_layer
+ );
+
+ gtk_widget_show_all (main_window);
+
+ gtk_main();
+
+ return 0;
+}
diff --git a/saedit/main.h b/saedit/main.h
new file mode 100644
index 0000000..1e482f3
--- /dev/null
+++ b/saedit/main.h
@@ -0,0 +1,51 @@
+#ifndef _MAIN_H_
+#define _MAIN_H_
+
+#include <gtk/gtk.h>
+#include "treefolderview.h"
+#include "context.h"
+#include "interactor.h"
+#include "spritedrawingarea.h"
+
+GtkWidget *main_window;
+GtkWidget *d_area;
+GtkWidget *source_view;
+GtkWidget *tf_view;
+GtkWidget *tbtn_play;
+
+GtkComboBox *cb_actions;
+GtkComboBoxText *cb_directions;
+GtkListStore *store_actions;
+GtkAdjustment *zoom_adj;
+
+SpriteContext *context;
+Interactor *interactor;
+
+SDALayer *tile_grid_layer;
+SDALayer *pixel_grid_layer;
+
+void
+buffer_set_modified (gboolean modified);
+
+gboolean
+buffer_get_modified (void);
+
+void
+release_context (void);
+
+void
+intr_updated (Interactor *interactor);
+
+void
+update_window_title (void);
+
+GtkWidget *
+window_main_get_source_view (void);
+
+void
+update_window_title (void);
+
+void
+release_context (void);
+
+#endif
diff --git a/saedit/spritedrawingarea/sdalayer.c b/saedit/spritedrawingarea/sdalayer.c
new file mode 100644
index 0000000..9794b80
--- /dev/null
+++ b/saedit/spritedrawingarea/sdalayer.c
@@ -0,0 +1,271 @@
+#include "spritedrawingarea.h"
+#include "sdalayer.h"
+#include "sdalayerprivate.h"
+
+enum {
+ PROP_0,
+ PROP_VISIBLE,
+ PROP_OFFSET_X,
+ PROP_OFFSET_Y,
+ PROP_DRAW_FUNC,
+ PROP_USER_DATA,
+ PROP_Z_INDEX,
+ PROP_COUNT
+};
+
+static void
+sda_layer_get_property (
+ GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec
+) {
+ SDALayer *layer = SDA_LAYER (object);
+ SDALayerPrivate *priv = layer->priv;
+
+ switch (prop_id) {
+ case PROP_VISIBLE:
+ g_value_set_boolean (
+ value, priv->visible
+ );
+ break;
+
+ case PROP_OFFSET_X:
+ g_value_set_int (
+ value, priv->offset_x
+ );
+ break;
+
+ case PROP_OFFSET_Y:
+ g_value_set_int (
+ value, priv->offset_y
+ );
+ break;
+
+ case PROP_Z_INDEX:
+ g_value_set_int (
+ value, priv->z_index
+ );
+ break;
+
+ case PROP_DRAW_FUNC:
+ g_value_set_pointer (
+ value, (gpointer) priv->draw_func
+ );
+ break;
+
+ case PROP_USER_DATA:
+ g_value_set_pointer (
+ value, priv->user_data
+ );
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+sda_layer_set_property (
+ GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec
+) {
+ SDALayer *layer = SDA_LAYER (object);
+ SDALayerPrivate *priv = layer->priv;
+
+ switch (prop_id) {
+ case PROP_VISIBLE:
+ priv->visible = g_value_get_boolean (value);
+ break;
+
+ case PROP_OFFSET_X:
+ priv->offset_x = g_value_get_int (value);
+ break;
+
+ case PROP_OFFSET_Y:
+ priv->offset_y = g_value_get_int (value);
+ break;
+
+ case PROP_Z_INDEX:
+ priv->z_index = g_value_get_int (value);
+ break;
+
+ case PROP_DRAW_FUNC:
+ priv->draw_func = (SDALayerDrawFunc) g_value_get_pointer (value);
+ break;
+
+ case PROP_USER_DATA:
+ priv->user_data = g_value_get_pointer (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+void
+sda_layer_init (
+ SDALayer *layer,
+ SDALayerClass *class
+) {
+ layer->priv = G_TYPE_INSTANCE_GET_PRIVATE (
+ layer,
+ SDA_TYPE_LAYER,
+ SDALayerPrivate
+ );
+
+ layer->priv->draw_func = NULL;
+ layer->priv->user_data = NULL;
+}
+
+void
+sda_layer_class_init (
+ SDALayerClass *klass,
+ gpointer class_data
+) {
+ GObjectClass *object_class;
+
+ g_type_class_add_private (klass, sizeof (SDALayerPrivate));
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->set_property = sda_layer_set_property;
+ object_class->get_property = sda_layer_get_property;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_VISIBLE,
+ g_param_spec_boolean (
+ "visible",
+ "Visible",
+ "Whether this layer is visible",
+ TRUE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT
+ )
+ );
+
+ g_object_class_install_property (
+ object_class,
+ PROP_OFFSET_X,
+ g_param_spec_int (
+ "offset-x",
+ "Offset X",
+ "The X coordinate of the layer offset",
+ -SPRITE_DRAWING_AREA_FIELD_SIZE,
+ SPRITE_DRAWING_AREA_FIELD_SIZE,
+ 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT
+ )
+ );
+
+ g_object_class_install_property (
+ object_class,
+ PROP_OFFSET_Y,
+ g_param_spec_int (
+ "offset-y",
+ "Offset y",
+ "The Y coordinate of the layer offset",
+ -SPRITE_DRAWING_AREA_FIELD_SIZE,
+ SPRITE_DRAWING_AREA_FIELD_SIZE,
+ 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT
+ )
+ );
+
+ g_object_class_install_property (
+ object_class,
+ PROP_DRAW_FUNC,
+ g_param_spec_pointer (
+ "draw-func",
+ "Draw function",
+ "A function invoked to draw the layer",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
+ )
+ );
+
+ g_object_class_install_property (
+ object_class,
+ PROP_USER_DATA,
+ g_param_spec_pointer (
+ "user-data",
+ "User data",
+ "Data that will be propagated to the draw"
+ " function upon call",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT
+ )
+ );
+
+ g_object_class_install_property (
+ object_class,
+ PROP_Z_INDEX,
+ g_param_spec_int (
+ "z-index",
+ "Z-index",
+ "Z-index of the layer",
+ -1024, 1024, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT
+ )
+ );
+}
+
+GType
+sda_layer_get_type (void) {
+ static GType sda_layer_type = 0;
+
+ if (sda_layer_type == 0) {
+ const GTypeInfo sda_layer_info = {
+ sizeof (SDALayerClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) sda_layer_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (SDALayer),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) sda_layer_init,
+ NULL, /* value_table */
+ };
+
+ sda_layer_type = g_type_register_static (
+ G_TYPE_OBJECT,
+ "SDALayer",
+ &sda_layer_info,
+ 0
+ );
+ }
+
+ return sda_layer_type;
+}
+
+SDALayer *sda_layer_new (
+ SDALayerDrawFunc draw_func,
+ gpointer user_data
+) {
+ return g_object_new (
+ SDA_TYPE_LAYER,
+ "draw-func", draw_func,
+ "user-data", user_data,
+ NULL
+ );
+}
+
+gint
+sda_layer_compare_by_z_index (
+ const SDALayer *a,
+ const SDALayer *b
+) {
+ return a->priv->z_index - b->priv->z_index;
+}
+
+void
+sda_layer_set_z_index (SDALayer *layer, gint z_index) {
+ g_object_set (layer, "z-index", z_index, NULL);
+}
+
+void
+sda_layer_set_visible (SDALayer *layer, gboolean visible) {
+ g_object_set (layer, "visible", visible, NULL);
+}
diff --git a/saedit/spritedrawingarea/sdalayer.h b/saedit/spritedrawingarea/sdalayer.h
new file mode 100644
index 0000000..e1bac04
--- /dev/null
+++ b/saedit/spritedrawingarea/sdalayer.h
@@ -0,0 +1,54 @@
+#ifndef _SDALAYER_H_
+#define _SDALAYER_H_
+
+#include <glib-object.h>
+#include <cairo.h>
+
+G_BEGIN_DECLS
+
+#define SDA_TYPE_LAYER (sda_layer_get_type ())
+#define SDA_LAYER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SDA_TYPE_LAYER, SDALayer))
+#define SDA_LAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SDA_TYPE_LAYER, SDALayerClass))
+#define IS_SDA_LAYER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SDA_TYPE_LAYER))
+#define IS_SDA_LAYER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SDA_TYPE_LAYER))
+#define SDA_LAYER_GET_CLASS(obj) ((obj), SDA_TYPE_LAYER, SDALayerClass)
+
+typedef struct _SDALayer SDALayer;
+typedef struct _SDALayerClass SDALayerClass;
+typedef struct _SDALayerPrivate SDALayerPrivate;
+
+struct _SDALayerClass {
+ GObjectClass parent_class;
+};
+
+GType
+sda_layer_get_type (void);
+
+typedef void
+(* SDALayerDrawFunc) (
+ SDALayer *layer,
+ cairo_t *cr,
+ gpointer user_data
+);
+
+SDALayer *
+sda_layer_new (
+ SDALayerDrawFunc draw_func,
+ gpointer user_data
+);
+
+gint
+sda_layer_compare_by_z_index (
+ const SDALayer *a,
+ const SDALayer *b
+);
+
+void
+sda_layer_set_z_index (SDALayer *layer, gint z_index);
+
+void
+sda_layer_set_visible (SDALayer *layer, gboolean visible);
+
+G_END_DECLS
+
+#endif
diff --git a/saedit/spritedrawingarea/sdalayerprivate.h b/saedit/spritedrawingarea/sdalayerprivate.h
new file mode 100644
index 0000000..daf47bc
--- /dev/null
+++ b/saedit/spritedrawingarea/sdalayerprivate.h
@@ -0,0 +1,19 @@
+#ifndef _SDALAYERPRIVATE_H_
+#define _SDALAYERPRIVATE_H_
+
+struct _SDALayerPrivate {
+ gboolean visible;
+ gint offset_x, offset_y;
+ gint z_index;
+
+ SDALayerDrawFunc draw_func;
+ gpointer user_data;
+};
+
+struct _SDALayer {
+ GObject object;
+
+ SDALayerPrivate *priv;
+};
+
+#endif
diff --git a/saedit/spritedrawingarea/spritedrawingarea.c b/saedit/spritedrawingarea/spritedrawingarea.c
new file mode 100644
index 0000000..376c8e1
--- /dev/null
+++ b/saedit/spritedrawingarea/spritedrawingarea.c
@@ -0,0 +1,437 @@
+#include "spritedrawingarea.h"
+#include "sdalayer.h"
+#include "sdalayerprivate.h"
+
+struct _SpriteDrawingAreaPrivate {
+ gint center_x, center_y;
+ gdouble scale_factor;
+
+ gboolean drag_active;
+ gdouble drag_x, drag_y;
+
+ GList *layers;
+};
+
+enum {
+ PROP_0,
+ PROP_CENTER_X,
+ PROP_CENTER_Y,
+ PROP_SCALE_FACTOR,
+ PROP_COUNT
+};
+
+void
+sprite_drawing_area_get_center (
+ SpriteDrawingArea *sdarea,
+ gint *center_x,
+ gint *center_y
+) {
+ g_object_get (
+ sdarea,
+ "center-x", center_x,
+ "center-y", center_y,
+ NULL
+ );
+}
+
+void
+sprite_drawing_area_set_center (
+ SpriteDrawingArea *sdarea,
+ gint center_x,
+ gint center_y
+) {
+ g_object_set (
+ sdarea,
+ "center-x", center_x,
+ "center-y", center_y,
+ NULL
+ );
+}
+
+gdouble
+sprite_drawing_area_get_scale_factor (
+ SpriteDrawingArea *sdarea
+) {
+ gdouble result;
+
+ g_object_get (
+ sdarea,
+ "scale-factor", &result,
+ NULL
+ );
+
+ return result;
+}
+
+void
+sprite_drawing_area_set_scale_factor (
+ SpriteDrawingArea *sdarea,
+ gdouble scale_factor
+) {
+ g_object_set (
+ sdarea,
+ "scale-factor", scale_factor,
+ NULL
+ );
+}
+
+static gboolean
+_sprite_drawing_area_draw_handler (
+ GtkWidget *widget,
+ cairo_t *cr,
+ gpointer user_data
+) {
+ SpriteDrawingArea *sdarea = SPRITE_DRAWING_AREA (widget);
+ GList *l;
+
+ gint width = gtk_widget_get_allocated_width (widget);
+ gint height = gtk_widget_get_allocated_height (widget);
+
+ gdouble scale = sprite_drawing_area_get_scale_factor (sdarea);
+
+ gint center_x, center_y;
+
+ sprite_drawing_area_get_center (sdarea, &center_x, &center_y);
+
+ cairo_translate (
+ cr,
+ 0.5 * (gdouble) width,
+ 0.5 * (gdouble) height
+ );
+
+ cairo_scale (cr, scale, scale);
+
+ cairo_translate (
+ cr,
+ -center_x,
+ -center_y
+ );
+
+ sdarea->priv->layers = g_list_sort (
+ sdarea->priv->layers,
+ (GCompareFunc) sda_layer_compare_by_z_index
+ );
+
+ for (l = sdarea->priv->layers; l != NULL; l = l->next) {
+ SDALayer *layer = SDA_LAYER (l->data);
+
+ if (!layer->priv->visible)
+ continue;
+
+ cairo_save (cr);
+
+ cairo_translate (
+ cr,
+ layer->priv->offset_x,
+ layer->priv->offset_y
+ );
+
+ if (layer->priv->draw_func != NULL)
+ layer->priv->draw_func (layer, cr, layer->priv->user_data);
+
+ cairo_restore (cr);
+ }
+
+ return FALSE;
+}
+
+static void
+sprite_drawing_area_get_property (
+ GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec
+) {
+ SpriteDrawingArea *sdarea = SPRITE_DRAWING_AREA (object);
+
+ switch (prop_id) {
+ case PROP_CENTER_X:
+ g_value_set_int (
+ value, sdarea->priv->center_x
+ );
+ break;
+
+ case PROP_CENTER_Y:
+ g_value_set_int (
+ value, sdarea->priv->center_y
+ );
+ break;
+
+ case PROP_SCALE_FACTOR:
+ g_value_set_double (
+ value, sdarea->priv->scale_factor
+ );
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+sprite_drawing_area_set_property (
+ GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec
+) {
+ SpriteDrawingArea *sdarea = SPRITE_DRAWING_AREA (object);
+
+ switch (prop_id) {
+ case PROP_CENTER_X:
+ sdarea->priv->center_x = g_value_get_int (value);
+ break;
+
+ case PROP_CENTER_Y:
+ sdarea->priv->center_y = g_value_get_int (value);
+ break;
+
+ case PROP_SCALE_FACTOR:
+ sdarea->priv->scale_factor = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+gboolean
+_sprite_drawing_area_drag_start_callback (
+ GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data
+) {
+ SpriteDrawingArea *sdarea = SPRITE_DRAWING_AREA (widget);
+
+ if (event->button.button != 1)
+ return FALSE;
+
+ sdarea->priv->drag_active = TRUE;
+ sdarea->priv->drag_x = event->button.x;
+ sdarea->priv->drag_y = event->button.y;
+
+ return FALSE;
+}
+
+gboolean
+_sprite_drawing_area_drag_end_callback (
+ GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data
+) {
+ SpriteDrawingArea *sdarea = SPRITE_DRAWING_AREA (widget);
+
+ if (event->button.button != 1)
+ return FALSE;
+
+ sdarea->priv->drag_active = FALSE;
+ return FALSE;
+}
+
+gboolean
+_sprite_drawing_area_drag_motion_callback (
+ GtkWidget *widget,
+ GdkEvent *event,
+ gpointer user_data
+) {
+ SpriteDrawingArea *sdarea = SPRITE_DRAWING_AREA (widget);
+
+ gdouble event_x, event_y;
+
+ gint center_x, center_y;
+ gint delta_x, delta_y;
+
+ gdouble scale_factor;
+
+ if ((event->motion.state & GDK_BUTTON1_MASK) == 0)
+ return FALSE;
+
+ if (!sdarea->priv->drag_active)
+ return FALSE;
+
+ event_x = event->motion.x;
+ event_y = event->motion.y;
+
+ sprite_drawing_area_get_center (sdarea, &center_x, &center_y);
+
+ scale_factor = sprite_drawing_area_get_scale_factor (sdarea);
+
+ delta_x =
+ (gint) (event_x / scale_factor + 0.5) - (gint) (sdarea->priv->drag_x / scale_factor + 0.5);
+ delta_y =
+ (gint) (event_y / scale_factor + 0.5) - (gint) (sdarea->priv->drag_y / scale_factor + 0.5);
+
+ sprite_drawing_area_set_center (
+ sdarea,
+ center_x - delta_x,
+ center_y - delta_y
+ );
+
+ sdarea->priv->drag_x = event_x;
+ sdarea->priv->drag_y = event_y;
+
+ gtk_widget_queue_draw (widget);
+ return FALSE;
+}
+
+static void
+sprite_drawing_area_init (
+ SpriteDrawingArea *sdarea,
+ SpriteDrawingAreaClass *klass
+) {
+ /* Setting up private */
+ sdarea->priv = G_TYPE_INSTANCE_GET_PRIVATE (
+ sdarea,
+ TYPE_SPRITE_DRAWING_AREA,
+ SpriteDrawingAreaPrivate
+ );
+
+ sdarea->priv->layers = NULL;
+
+ g_signal_connect (
+ sdarea,
+ "button-press-event",
+ (GCallback) _sprite_drawing_area_drag_start_callback,
+ NULL
+ );
+
+ g_signal_connect (
+ sdarea,
+ "button-release-event",
+ (GCallback) _sprite_drawing_area_drag_end_callback,
+ NULL
+ );
+
+ g_signal_connect (
+ sdarea,
+ "motion-notify-event",
+ (GCallback) _sprite_drawing_area_drag_motion_callback,
+ NULL
+ );
+}
+
+static void
+sprite_drawing_area_class_init (
+ SpriteDrawingAreaClass *klass,
+ gpointer class_data
+) {
+ GObjectClass *object_class;
+
+ g_type_class_add_private (klass, sizeof (SpriteDrawingAreaPrivate));
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->set_property = sprite_drawing_area_set_property;
+ object_class->get_property = sprite_drawing_area_get_property;
+
+ g_signal_override_class_handler (
+ "draw",
+ TYPE_SPRITE_DRAWING_AREA,
+ (GCallback) _sprite_drawing_area_draw_handler
+ );
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CENTER_X,
+ g_param_spec_int (
+ "center-x",
+ "Center X",
+ "The X coordinate of the point on the field "
+ "camera is centered on, in pixels",
+ -SPRITE_DRAWING_AREA_FIELD_SIZE,
+ SPRITE_DRAWING_AREA_FIELD_SIZE,
+ 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT
+ )
+ );
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CENTER_Y,
+ g_param_spec_int (
+ "center-y",
+ "Center Y",
+ "The Y coordinate of the point on the field "
+ "camera is centered on, in pixels",
+ -SPRITE_DRAWING_AREA_FIELD_SIZE,
+ SPRITE_DRAWING_AREA_FIELD_SIZE,
+ 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT
+ )
+ );
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SCALE_FACTOR,
+ g_param_spec_double (
+ "scale-factor",
+ "Scale factor",
+ "The scaling factor that is used in making "
+ "the camera zooming effect",
+ 0.01, 100.0, 1.0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT
+ )
+ );
+}
+
+GType
+sprite_drawing_area_get_type (void) {
+ static GType sdarea_type = 0;
+
+ if (sdarea_type == 0) {
+ const GTypeInfo sdarea_info = {
+ sizeof (SpriteDrawingAreaClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) sprite_drawing_area_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (SpriteDrawingArea),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) sprite_drawing_area_init,
+ NULL, /* value_table */
+ };
+
+ sdarea_type = g_type_register_static (
+ GTK_TYPE_DRAWING_AREA,
+ "SpriteDrawingArea",
+ &sdarea_info,
+ 0
+ );
+
+ }
+
+ return sdarea_type;
+}
+
+GtkWidget *
+sprite_drawing_area_new (void) {
+ GtkWidget *sdarea = GTK_WIDGET (
+ g_object_new (TYPE_SPRITE_DRAWING_AREA, NULL)
+ );
+ return sdarea;
+}
+
+void
+sprite_drawing_area_add_layer (
+ SpriteDrawingArea *sdarea,
+ SDALayer *layer
+) {
+ sdarea->priv->layers = g_list_insert_sorted (
+ sdarea->priv->layers,
+ layer,
+ (GCompareFunc) sda_layer_compare_by_z_index
+ );
+}
+
+void
+sprite_drawing_area_remove_layer (
+ SpriteDrawingArea *sdarea,
+ SDALayer *layer
+) {
+ sdarea->priv->layers = g_list_remove (
+ sdarea->priv->layers,
+ layer
+ );
+}
diff --git a/saedit/spritedrawingarea/spritedrawingarea.h b/saedit/spritedrawingarea/spritedrawingarea.h
new file mode 100644
index 0000000..ee3aa6d
--- /dev/null
+++ b/saedit/spritedrawingarea/spritedrawingarea.h
@@ -0,0 +1,71 @@
+#ifndef __SPRITE_DRAWING_AREA_H__
+#define __SPRITE_DRAWING_AREA_H__
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include "sdalayer.h"
+
+G_BEGIN_DECLS
+
+#define TYPE_SPRITE_DRAWING_AREA (sprite_drawing_area_get_type ())
+#define SPRITE_DRAWING_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_SPRITE_DRAWING_AREA, SpriteDrawingArea))
+#define SPRITE_DRAWING_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_SPRITE_DRAWING_AREA, SpriteDrawingAreaClass))
+#define IS_SPRITE_DRAWING_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_SPRITE_DRAWING_AREA))
+#define IS_SPRITE_DRAWING_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_SPRITE_DRAWING_AREA))
+#define SPRITE_DRAWING_AREA_GET_CLASS(obj) ((obj), TYPE_SPRITE_DRAWING_AREA, SpriteDrawingAreaClass)
+
+typedef struct _SpriteDrawingArea SpriteDrawingArea;
+typedef struct _SpriteDrawingAreaPrivate SpriteDrawingAreaPrivate;
+typedef struct _SpriteDrawingAreaClass SpriteDrawingAreaClass;
+
+struct _SpriteDrawingArea {
+ GtkDrawingArea darea;
+
+ SpriteDrawingAreaPrivate *priv;
+};
+
+struct _SpriteDrawingAreaClass {
+ GtkDrawingAreaClass darea_class;
+
+ void (* draw_field) (
+ GtkWidget *sdarea,
+ gpointer data
+ );
+};
+
+static const gint SPRITE_DRAWING_AREA_FIELD_SIZE = 1024;
+
+GType
+sprite_drawing_area_get_type (void);
+
+GtkWidget*
+sprite_drawing_area_new (void);
+
+void
+sprite_drawing_area_set_scale_factor (
+ SpriteDrawingArea *sdarea,
+ gdouble scale_factor
+);
+
+void
+sprite_drawing_area_add_layer (
+ SpriteDrawingArea *sdarea,
+ SDALayer *layer
+);
+
+void
+sprite_drawing_area_remove_layer (
+ SpriteDrawingArea *sdarea,
+ SDALayer *layer
+);
+
+void
+sprite_drawing_area_set_center (
+ SpriteDrawingArea *sdarea,
+ gint center_x,
+ gint center_y
+);
+
+G_END_DECLS
+
+#endif
diff --git a/saedit/treefolderview/treefolderview.c b/saedit/treefolderview/treefolderview.c
new file mode 100644
index 0000000..e1b02f0
--- /dev/null
+++ b/saedit/treefolderview/treefolderview.c
@@ -0,0 +1,400 @@
+#include "treefolderview.h"
+#include "treefolderviewprivate.h"
+
+enum {
+ STORE_COLUMN_FILE_NAME,
+ STORE_COLUMN_IS_FOLDER,
+ STORE_COLUMN_FILE_ICON,
+ STORE_COLUMN_FILE_INFO,
+ STORE_COLUMN_WAS_EXPANDED,
+ STORE_COLUMN_COUNT
+};
+
+enum {
+ SIGNAL_FILE_ACTIVATED,
+ SIGNAL_COUNT
+};
+
+struct _TreeFolderViewPrivate {
+ gchar *filename;
+ GtkFileFilter *file_filter;
+};
+
+static guint tfview_signals [SIGNAL_COUNT] = { 0 };
+
+static GtkTreeStore *
+tree_folder_view_get_store (TreeFolderView *tfview) {
+ GtkTreeModelFilter *model;
+
+ model = GTK_TREE_MODEL_FILTER (gtk_tree_view_get_model (GTK_TREE_VIEW (tfview)));
+ return GTK_TREE_STORE (gtk_tree_model_filter_get_model (model));
+}
+
+static gboolean
+file_filter_filter_file_info (GtkFileFilter *filter, GFileInfo *info) {
+ gboolean result;
+ gchar *mimetype = NULL;
+ GtkFileFilterFlags required;
+ GtkFileFilterInfo filter_info = { 0, };
+
+ if (filter == NULL)
+ return TRUE;
+
+ required = gtk_file_filter_get_needed (filter);
+
+ filter_info.contains |= GTK_FILE_FILTER_DISPLAY_NAME;
+ filter_info.display_name = g_file_info_get_display_name (info);
+
+ if (required & GTK_FILE_FILTER_MIME_TYPE) {
+ const gchar *ctype = g_file_info_get_content_type (info);
+ if (ctype != NULL) {
+ mimetype = g_content_type_get_mime_type (ctype);
+ if (mimetype != NULL) {
+ filter_info.contains |= GTK_FILE_FILTER_MIME_TYPE;
+ filter_info.mime_type = mimetype;
+ }
+ }
+ }
+
+ if (required & GTK_FILE_FILTER_FILENAME) {
+ filter_info.filename = g_file_info_get_name (info);
+ filter_info.contains |= GTK_FILE_FILTER_FILENAME;
+ }
+
+ result = gtk_file_filter_filter (filter, &filter_info);
+
+ g_free(mimetype);
+
+ return result;
+}
+
+static gchar *
+tree_folder_view_get_file_path_from_tree_path (
+ TreeFolderView *tfview,
+ GtkTreePath *path
+) {
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ model = GTK_TREE_MODEL (tree_folder_view_get_store (tfview));
+ gtk_tree_model_get_iter (model, &iter, path);
+
+ return tree_folder_view_get_file_path_from_iter (tfview, &iter);
+}
+
+static gchar *
+tree_folder_view_get_file_path_from_iter (
+ TreeFolderView *tfview,
+ GtkTreeIter *file_iter
+) {
+ gchar *result, *data;
+ GPtrArray *names;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ GtkTreePath *_path;
+
+ model = GTK_TREE_MODEL (tree_folder_view_get_store (tfview));
+ _path = gtk_tree_model_get_path (model, file_iter);
+
+ names = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free);
+
+ while (gtk_tree_path_get_depth (_path) > 0) {
+ gtk_tree_model_get_iter (model, &iter, _path);
+ gtk_tree_model_get (
+ model, &iter,
+ STORE_COLUMN_FILE_NAME, &data, -1
+ );
+ g_ptr_array_insert (names, 0, data);
+ gtk_tree_path_up (_path);
+ }
+
+ g_ptr_array_insert (names, 0, g_strdup (tfview->priv->filename));
+ g_ptr_array_add (names, NULL);
+ result = g_strjoinv ("/", (gchar **) names->pdata);
+
+ g_ptr_array_free (names, TRUE);
+ gtk_tree_path_free (_path);
+
+ return result;
+}
+
+static void
+tree_folder_view_row_expanded (
+ GtkTreeView *tree_view,
+ GtkTreeIter *filter_iter,
+ GtkTreePath *path
+) {
+ gboolean w_exp;
+ gchar *file_path;
+ GtkTreeIter iter, citer;
+ GtkTreeStore *store;
+
+ gtk_tree_model_filter_convert_iter_to_child_iter (
+ GTK_TREE_MODEL_FILTER (gtk_tree_view_get_model (tree_view)),
+ &iter,
+ filter_iter
+ );
+
+ store = tree_folder_view_get_store (
+ TREE_FOLDER_VIEW (tree_view)
+ );
+
+ gtk_tree_model_get (
+ GTK_TREE_MODEL (store), &iter,
+ STORE_COLUMN_WAS_EXPANDED, &w_exp,
+ -1
+ );
+
+ if (w_exp)
+ return;
+
+ gtk_tree_store_set (
+ store, &iter,
+ STORE_COLUMN_WAS_EXPANDED, TRUE,
+ -1
+ );
+
+ if (!gtk_tree_model_iter_has_child (GTK_TREE_MODEL (store), &iter))
+ return;
+
+ gtk_tree_model_iter_children (GTK_TREE_MODEL (store), &citer, &iter);
+ do {
+ file_path = tree_folder_view_get_file_path_from_iter (
+ TREE_FOLDER_VIEW (tree_view),
+ &citer
+ );
+
+ tree_store_append_file_children (
+ store,
+ &citer,
+ file_path,
+ FALSE
+ );
+
+ g_free (file_path);
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &citer));
+}
+
+static gboolean
+tree_model_filter_file_visible_func (
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data
+) {
+ gboolean is_folder;
+ GFileInfo *info;
+ GtkFileFilter *filter;
+ TreeFolderView *tfview;
+
+ tfview = TREE_FOLDER_VIEW (data);
+
+ gtk_tree_model_get (
+ model, iter,
+ STORE_COLUMN_FILE_INFO, &info,
+ STORE_COLUMN_IS_FOLDER, &is_folder,
+ -1
+ );
+
+ if (info == NULL)
+ return FALSE;
+
+ if (g_file_info_get_is_hidden (info))
+ return FALSE;
+
+ if (is_folder)
+ return TRUE;
+
+ filter = tree_folder_view_get_filter (tfview);
+ return file_filter_filter_file_info (filter, info);
+}
+
+static void
+tree_store_append_file_children (
+ GtkTreeStore *store,
+ GtkTreeIter *iter,
+ const gchar *path,
+ gboolean expanded
+) {
+ gchar *npath;
+ const gchar *name;
+ GDir *dir;
+
+ dir = g_dir_open (path, 0, NULL);
+
+ if (dir == NULL)
+ return;
+
+ while ((name = g_dir_read_name (dir)) != NULL) {
+ npath = g_strconcat (path, "/", name, NULL);
+
+ tree_store_append_file_recursive (
+ store,
+ iter,
+ npath,
+ name,
+ expanded
+ );
+
+ g_free(npath);
+ }
+
+ g_dir_close (dir);
+}
+
+static gint
+tree_store_append_file_recursive (
+ GtkTreeStore *store,
+ GtkTreeIter *parent_iter,
+ const gchar *path,
+ const gchar *display_name,
+ gboolean append_children
+) {
+ GFile *file;
+ GFileInfo *info;
+ GtkTreeIter iter;
+
+ file = g_file_new_for_path (path);
+ info = g_file_query_info (file, "*", 0, NULL, NULL);
+ g_object_unref (file);
+
+ gtk_tree_store_append (store, &iter, parent_iter);
+ gtk_tree_store_set (
+ store, &iter,
+ STORE_COLUMN_FILE_NAME, display_name,
+ STORE_COLUMN_FILE_ICON, g_content_type_get_icon (g_file_info_get_content_type (info)),
+ STORE_COLUMN_FILE_INFO, info,
+ STORE_COLUMN_IS_FOLDER, FALSE,
+ STORE_COLUMN_WAS_EXPANDED, FALSE,
+ -1
+ );
+
+ if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
+ return 0;
+
+ gtk_tree_store_set (
+ store, &iter,
+ STORE_COLUMN_IS_FOLDER, TRUE,
+ STORE_COLUMN_FILE_ICON, g_themed_icon_new ("folder"), -1
+ );
+
+ if (!append_children)
+ return 1;
+
+ tree_store_append_file_children (store, &iter, path, FALSE);
+
+ return 1;
+}
+
+static gint
+tree_store_iter_compare_func (
+ GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer user_data
+) {
+ gboolean isf_a, isf_b;
+ gint cmp;
+ gchar *val_a, *val_b;
+
+ gtk_tree_model_get (model, a, STORE_COLUMN_IS_FOLDER, &isf_a, -1);
+ gtk_tree_model_get (model, b, STORE_COLUMN_IS_FOLDER, &isf_b, -1);
+
+ if (isf_a != isf_b)
+ return isf_b;
+
+ gtk_tree_model_get (model, a, STORE_COLUMN_FILE_NAME, &val_a, -1);
+ gtk_tree_model_get (model, b, STORE_COLUMN_FILE_NAME, &val_b, -1);
+
+ cmp = g_strcmp0 (val_a, val_b);
+ g_free (val_a);
+ g_free (val_b);
+
+ return cmp;
+}
+
+static void
+tree_folder_view_row_activated (
+ GtkTreeView *tree_view,
+ GtkTreePath *filter_path,
+ GtkTreeViewColumn *col
+) {
+ gboolean is_folder;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ GtkTreeStore *store;
+
+ path = gtk_tree_model_filter_convert_path_to_child_path (
+ GTK_TREE_MODEL_FILTER (gtk_tree_view_get_model (tree_view)),
+ filter_path
+ );
+
+ store = tree_folder_view_get_store (TREE_FOLDER_VIEW (tree_view));
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path);
+ gtk_tree_model_get (
+ GTK_TREE_MODEL (store), &iter,
+ STORE_COLUMN_IS_FOLDER, &is_folder, -1
+ );
+
+ if (is_folder)
+ return;
+
+ g_signal_emit (
+ tree_view,
+ tfview_signals[SIGNAL_FILE_ACTIVATED],
+ 0,
+ tree_folder_view_get_file_path_from_tree_path (
+ TREE_FOLDER_VIEW (tree_view),
+ path
+ )
+ );
+
+ gtk_tree_path_free (path);
+}
+
+void
+tree_folder_view_set_filename (
+ TreeFolderView *tfview,
+ const gchar *filename
+) {
+ GtkTreeStore *store = tree_folder_view_get_store (tfview);
+
+ /* TODO: values inside are not freed */
+ gtk_tree_store_clear (store);
+
+ g_free (tfview->priv->filename);
+ tfview->priv->filename = g_strdup (filename);
+
+ if (filename != NULL) {
+ tree_store_append_file_children (
+ store,
+ NULL,
+ filename,
+ TRUE
+ );
+ }
+}
+
+GtkFileFilter *
+tree_folder_view_get_filter (TreeFolderView *tfview) {
+ return tfview->priv->file_filter;
+}
+
+void
+tree_folder_view_set_filter (TreeFolderView *tfview, GtkFileFilter *filter) {
+ if (G_IS_OBJECT (tfview->priv->file_filter))
+ g_object_unref (tfview->priv->file_filter);
+
+ tfview->priv->file_filter = filter;
+ g_object_ref (filter);
+
+ gtk_tree_model_filter_refilter (
+ GTK_TREE_MODEL_FILTER (gtk_tree_view_get_model (GTK_TREE_VIEW (tfview))));
+}
+
+gchar *
+tree_folder_view_get_filename (TreeFolderView *tfview) {
+ return g_strdup (tfview->priv->filename);
+}
+
+#include "type.c"
diff --git a/saedit/treefolderview/treefolderview.h b/saedit/treefolderview/treefolderview.h
new file mode 100644
index 0000000..78aeefc
--- /dev/null
+++ b/saedit/treefolderview/treefolderview.h
@@ -0,0 +1,64 @@
+#ifndef __TREE_FOLDER_VIEW_H__
+#define __TREE_FOLDER_VIEW_H__
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TYPE_TREE_FOLDER_VIEW (tree_folder_view_get_type ())
+#define TREE_FOLDER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_TREE_FOLDER_VIEW, TreeFolderView))
+#define TREE_FOLDER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_TREE_FOLDER_VIEW, TreeFolderViewClass))
+#define IS_TREE_FOLDER_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_TREE_FOLDER_VIEW))
+#define IS_TREE_FOLDER_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_TREE_FOLDER_VIEW))
+#define TREE_FOLDER_VIEW_GET_CLASS(obj) ((obj), TYPE_TREE_FOLDER_VIEW, TreeFolderViewClass)
+
+typedef struct _TreeFolderView TreeFolderView;
+typedef struct _TreeFolderViewPrivate TreeFolderViewPrivate;
+typedef struct _TreeFolderViewClass TreeFolderViewClass;
+
+struct _TreeFolderView {
+ GtkTreeView tview;
+
+ TreeFolderViewPrivate *priv;
+};
+
+struct _TreeFolderViewClass {
+ GtkTreeViewClass tview_class;
+
+ void (* file_activated) (
+ TreeFolderView *tfview,
+ gchar *filename
+ );
+};
+
+GType
+tree_folder_view_get_type (void);
+
+GtkWidget*
+tree_folder_view_new (void);
+
+void
+tree_folder_view_set_filename (
+ TreeFolderView *tfview,
+ const gchar *filename
+);
+
+GtkFileFilter*
+tree_folder_view_get_filter (TreeFolderView *tfview);
+
+void
+tree_folder_view_set_filter (
+ TreeFolderView *tfview,
+ GtkFileFilter *filter
+);
+
+gchar *
+tree_folder_view_get_filename (TreeFolderView *tfview);
+
+gchar *
+tree_folder_view_get_display_name (TreeFolderView *tfview);
+
+G_END_DECLS
+
+#endif
diff --git a/saedit/treefolderview/treefolderviewprivate.h b/saedit/treefolderview/treefolderviewprivate.h
new file mode 100644
index 0000000..01119fc
--- /dev/null
+++ b/saedit/treefolderview/treefolderviewprivate.h
@@ -0,0 +1,45 @@
+#ifndef __TREE_FOLDER_VIEW_PRIVATE_H__
+#define __TREE_FOLDER_VIEW_PRIVATE_H__
+
+enum {
+ PROP_0,
+ PROP_FILTER,
+ PROP_FILENAME,
+ PROP_MODEL,
+ PROP_COUNT
+};
+
+static GtkTreeStore *
+tree_folder_view_get_store (TreeFolderView *tfview);
+
+static gchar *
+tree_folder_view_get_file_path_from_tree_path (
+ TreeFolderView *tfview,
+ GtkTreePath *path
+);
+
+static gchar *
+tree_folder_view_get_file_path_from_iter (
+ TreeFolderView *tfview,
+ GtkTreeIter *file_iter
+);
+
+static void
+tree_store_append_file_children (
+ GtkTreeStore *store,
+ GtkTreeIter *iter,
+ const gchar *path,
+ gboolean expanded
+);
+
+static gint
+tree_store_append_file_recursive (
+ GtkTreeStore *store,
+ GtkTreeIter *parent_iter,
+ const gchar *path,
+ const gchar *display_name,
+ gboolean append_children
+);
+
+#endif
+
diff --git a/saedit/treefolderview/type.c b/saedit/treefolderview/type.c
new file mode 100644
index 0000000..fbb0f21
--- /dev/null
+++ b/saedit/treefolderview/type.c
@@ -0,0 +1,250 @@
+static void
+tree_folder_view_init (
+ TreeFolderView *tfview,
+ TreeFolderViewClass *klass
+) {
+ GType *types;
+ GtkCellRenderer *renderer;
+ GtkTreeModel *model;
+ GtkTreeStore *store;
+ GtkTreeView *tview;
+ GtkTreeViewColumn *col;
+
+ tview = GTK_TREE_VIEW (tfview);
+
+ /* Setting up TreeView properties */
+ gtk_tree_view_set_headers_visible (tview, FALSE);
+ gtk_tree_view_set_enable_tree_lines (tview, TRUE);
+
+ /* Setting up only column */
+ col = gtk_tree_view_column_new ();
+
+ /* Filename renderer */
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_end (col, renderer, TRUE);
+ gtk_tree_view_column_set_attributes (
+ col,
+ renderer,
+ "text", STORE_COLUMN_FILE_NAME,
+ NULL
+ );
+
+ /* Filetype renderer */
+ renderer = gtk_cell_renderer_pixbuf_new();
+ gtk_tree_view_column_pack_end (col, renderer, FALSE);
+ gtk_tree_view_column_set_attributes (
+ col,
+ renderer,
+ "gicon", STORE_COLUMN_FILE_ICON,
+ NULL
+ );
+
+ gtk_tree_view_append_column (tview, col);
+
+ /* Setting up TreeStore */
+ types = g_new (GType, STORE_COLUMN_COUNT);
+ types [STORE_COLUMN_FILE_NAME] = G_TYPE_STRING;
+ types [STORE_COLUMN_FILE_ICON] = G_TYPE_ICON;
+ types [STORE_COLUMN_FILE_INFO] = G_TYPE_FILE_INFO;
+ types [STORE_COLUMN_IS_FOLDER] = G_TYPE_BOOLEAN;
+ types [STORE_COLUMN_WAS_EXPANDED] = G_TYPE_BOOLEAN;
+
+ store = gtk_tree_store_newv (STORE_COLUMN_COUNT, types);
+ g_free(types);
+
+ gtk_tree_sortable_set_sort_func (
+ GTK_TREE_SORTABLE (store),
+ STORE_COLUMN_FILE_NAME,
+ (GtkTreeIterCompareFunc) tree_store_iter_compare_func,
+ NULL,
+ NULL
+ );
+
+ gtk_tree_sortable_set_sort_column_id (
+ GTK_TREE_SORTABLE (store),
+ STORE_COLUMN_FILE_NAME,
+ GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID
+ );
+
+ /* Setting up TreeModelFilter */
+ model = gtk_tree_model_filter_new (GTK_TREE_MODEL (store), NULL);
+ gtk_tree_model_filter_set_visible_func (
+ GTK_TREE_MODEL_FILTER (model),
+ (GtkTreeModelFilterVisibleFunc) tree_model_filter_file_visible_func,
+ (gpointer) tview,
+ NULL
+ );
+ gtk_tree_view_set_model (tview, model);
+
+ /* Setting up private */
+ tfview->priv = G_TYPE_INSTANCE_GET_PRIVATE (
+ tfview,
+ TYPE_TREE_FOLDER_VIEW,
+ TreeFolderViewPrivate
+ );
+}
+
+static void
+tree_folder_view_set_property (
+ GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec
+) {
+ TreeFolderView *tfview = TREE_FOLDER_VIEW (object);
+
+ switch (prop_id) {
+ case PROP_FILTER:
+ tree_folder_view_set_filter (
+ tfview,
+ GTK_FILE_FILTER (g_value_get_object (value))
+ );
+ break;
+ case PROP_FILENAME:
+ tree_folder_view_set_filename (
+ tfview,
+ g_value_get_string (value)
+ );
+ break;
+ case PROP_MODEL:
+ g_warning(
+ "\"model\" property of GtkTreeView is overriden "
+ "in TreeFolderView and shouldn't be changed"
+ );
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+tree_folder_view_get_property (
+ GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec
+) {
+ TreeFolderView *tfview = TREE_FOLDER_VIEW (object);
+
+ switch (prop_id) {
+ case PROP_FILTER:
+ g_value_set_object (
+ value, tree_folder_view_get_filter (tfview)
+ );
+ break;
+ case PROP_FILENAME:
+ g_value_set_string (
+ value, tree_folder_view_get_filename (tfview)
+ );
+ break;
+ case PROP_MODEL:
+ g_value_set_object (
+ value, gtk_tree_view_get_model (GTK_TREE_VIEW (tfview))
+ );
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+tree_folder_view_class_init (
+ TreeFolderViewClass *klass,
+ gpointer class_data
+) {
+ GObjectClass *object_class;
+ GtkTreeViewClass *tree_view_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ tree_view_class = GTK_TREE_VIEW_CLASS (klass);
+
+ tree_view_class->row_activated = tree_folder_view_row_activated;
+ tree_view_class->row_expanded = tree_folder_view_row_expanded;
+
+ tfview_signals [SIGNAL_FILE_ACTIVATED] = g_signal_new (
+ "file-activated",
+ TYPE_TREE_FOLDER_VIEW,
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (TreeFolderViewClass, file_activated),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING
+ );
+
+ g_type_class_add_private (klass, sizeof (TreeFolderViewPrivate));
+
+ object_class->set_property = tree_folder_view_set_property;
+ object_class->get_property = tree_folder_view_get_property;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FILTER,
+ g_param_spec_object (
+ "filter",
+ "File filter",
+ "File filter for selecting "
+ "which files should be displayed.",
+ GTK_TYPE_FILE_FILTER,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT
+ )
+ );
+
+ g_object_class_install_property (
+ object_class,
+ PROP_FILENAME,
+ g_param_spec_string (
+ "filename",
+ "Folder filename",
+ "Full path to a folder "
+ "which contents are displayed.",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT
+ )
+ );
+
+ g_object_class_override_property (
+ object_class,
+ PROP_MODEL,
+ "model"
+ );
+}
+
+GType
+tree_folder_view_get_type (void) {
+ static GType tfview_type = 0;
+
+ if (tfview_type == 0) {
+ const GTypeInfo tfview_info = {
+ sizeof (TreeFolderViewClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) tree_folder_view_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (TreeFolderView),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) tree_folder_view_init,
+ NULL, /* value_table */
+ };
+
+ tfview_type = g_type_register_static (
+ GTK_TYPE_TREE_VIEW,
+ "TreeFolderView",
+ &tfview_info,
+ 0
+ );
+
+ }
+
+ return tfview_type;
+}
+
+GtkWidget *
+tree_folder_view_new (void) {
+ GtkWidget *tfview = GTK_WIDGET (
+ g_object_new (TYPE_TREE_FOLDER_VIEW, NULL)
+ );
+ return tfview;
+}
diff --git a/saedit/xml.c b/saedit/xml.c
new file mode 100644
index 0000000..90c4d76
--- /dev/null
+++ b/saedit/xml.c
@@ -0,0 +1,385 @@
+#include "xml.h"
+#include <string.h>
+
+gchar **
+xml_attr_new (
+ const gchar *name,
+ const gchar *value
+) {
+ gchar **attr = g_new0(gchar*, 2);
+ attr[0] = g_strdup(name);
+ attr[1] = g_strdup(value);
+ return attr;
+}
+
+gchar *
+xml_node_get_attr_value (
+ const XMLNode *node,
+ const gchar *attr_name
+) {
+ gchar **attr = node->attributes;
+ guint i;
+ for (i = 0; i < g_strv_length(attr); i += 2)
+ if (g_str_equal(attr[i], attr_name))
+ return g_strdup (attr[i + 1]);
+ return NULL;
+}
+
+gint
+xml_node_get_int_attr_value (
+ const XMLNode *node,
+ const gchar *attr_name,
+ gint retval
+) {
+ gchar *val = xml_node_get_attr_value (node, attr_name);
+
+ if (val != NULL) {
+ try_strtoint (val, &retval);
+ g_free (val);
+ }
+
+ return retval;
+}
+
+gint
+xml_node_get_int_attr_value_limited (
+ const XMLNode *node,
+ const gchar *attr_name,
+ gint retval,
+ gint lower,
+ gint upper
+) {
+ g_assert (lower <= upper);
+
+ retval = xml_node_get_int_attr_value (
+ node, attr_name, retval
+ );
+
+ if (retval < lower)
+ retval = lower;
+
+ if (retval > upper)
+ retval = upper;
+ return retval;
+}
+
+gint
+xml_node_compare_with_name_func (
+ gconstpointer a,
+ gconstpointer b
+) {
+ return g_strcmp0((gchar *) b, ((XMLNode *) a)->name);
+}
+
+gint
+xml_node_compare_with_action_node_by_imageset_name_func (
+ gconstpointer a,
+ gconstpointer b
+) {
+ return g_strcmp0("action", ((XMLNode *) a)->name) ||
+ g_strcmp0((gchar *) b, xml_node_get_attr_value((XMLNode *) a, "imageset"));
+}
+
+gint
+xml_node_compare_with_attr_func (
+ const XMLNode *node,
+ const gchar **attr
+) {
+ return g_strcmp0(attr[1],
+ xml_node_get_attr_value(node, attr[0]));
+}
+
+static GMarkupParser parser;
+
+void _xml_free_g_func (XMLNode *node, gpointer user_data) {
+ xml_free (node);
+}
+
+void xml_free (XMLNode *node) {
+ g_free (node->name);
+
+ g_free (node->text);
+
+ g_strfreev (node->attributes);
+
+ g_list_foreach (node->sub_nodes, (GFunc) _xml_free_g_func, NULL);
+ g_list_free (node->sub_nodes);
+
+ g_slice_free (XMLNode, node);
+}
+
+static void _start_root_element_cb ( GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error
+) {
+ XMLNode **node = (XMLNode **) user_data;
+ XMLNode *p;
+ GArray *attributes;
+
+ g_assert (node != NULL);
+
+ p = g_slice_new0 (XMLNode);
+ p->name = g_strdup (element_name);
+
+ attributes = g_array_new (TRUE, TRUE, sizeof (gchar *));
+ while (*attribute_names != NULL && *attribute_values != NULL) {
+ gchar *p2;
+ p2 = g_strdup (*attribute_names++);
+ g_array_append_val (attributes, p2);
+ p2 = g_strdup (*attribute_values++);
+ g_array_append_val (attributes, p2);
+ }
+
+ p->attributes = (gchar **) g_array_free (attributes, FALSE);
+
+ g_markup_parse_context_push (context, &parser, p);
+ *node = p;
+}
+
+
+static void _start_element_cb (
+ GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error
+) {
+ XMLNode *p, *node = (XMLNode *) user_data;
+ GArray *attributes;
+ gint char_n, line_n;
+
+ if (node->text) {
+ g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, " ");
+ return;
+ }
+
+ p = g_slice_new0 (XMLNode);
+
+ node->sub_nodes = g_list_append (node->sub_nodes, p);
+ g_markup_parse_context_push (context, &parser, p);
+
+ p->name = g_strdup (element_name);
+
+ attributes = g_array_new (TRUE, TRUE, sizeof (gchar *));
+ while (*attribute_names != NULL && *attribute_values != NULL) {
+ gchar *p2;
+ p2 = g_strdup (*attribute_names++);
+ g_array_append_val (attributes, p2);
+ p2 = g_strdup (*attribute_values++);
+ g_array_append_val (attributes, p2);
+ }
+
+ p->attributes = (gchar **)g_array_free (attributes, FALSE);
+
+ g_markup_parse_context_get_position(context, &line_n, &char_n);
+ p->line_no = line_n - (char_n <= 1 ? 1 : 0);
+}
+
+static void
+_end_element_cb (
+ GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error
+) {
+ XMLNode *p = (XMLNode *) g_markup_parse_context_pop (context);
+
+ if (p->text && p->sub_nodes) {
+ g_warning ("Error");
+ }
+
+ if (p->text == NULL && p->sub_nodes == NULL) {
+ p->text = g_strdup ("");
+ }
+}
+
+static gboolean
+_is_space (
+ const gchar *text,
+ gsize text_len
+) {
+ gsize i = 0;
+
+ for (i = 0; text[i] != '\0' && i < text_len; i++) {
+ switch (text[i]) {
+ case '\t':
+ case ' ':
+ case '\n':
+ case '\r':
+ continue;
+ default:
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+_text_cb (
+ GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error
+) {
+ XMLNode *p = (XMLNode *)user_data;
+
+ if (_is_space (text, text_len)) {
+ return;
+ }
+
+ if (p->sub_nodes || p->text) {
+ g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, " ");
+ return;
+ }
+
+ p->text = g_strndup (text, text_len);
+}
+
+static GMarkupParser parser = {
+ _start_element_cb,
+ _end_element_cb,
+ _text_cb,
+ 0,
+ 0,
+};
+
+XMLNode *
+xml_parse_file (const gchar *filename) {
+ gboolean retval = FALSE;
+ GError *error = NULL;
+ GMarkupParseContext *context;
+ XMLNode *node;
+
+ static const GMarkupParser root_parser = {
+ _start_root_element_cb,
+ _end_element_cb,
+ _text_cb,
+ 0,
+ 0,
+ };
+
+ FILE *pf = fopen (filename, "r");
+
+ if (pf == NULL) {
+ return NULL;
+ }
+
+ do {
+ context = g_markup_parse_context_new (&root_parser, 0, &node, 0);
+
+ while (!feof (pf)) {
+ gchar buf[1024];
+ gssize len = 0;
+
+ len = fread (buf, 1, sizeof (buf), pf);
+ retval = g_markup_parse_context_parse (context, buf, len, &error);
+
+ if (!retval)
+ break;
+ }
+ fclose (pf);
+
+ if (!retval)
+ break;
+
+ retval = g_markup_parse_context_end_parse (context, &error);
+ if (!retval)
+ break;
+
+ g_markup_parse_context_free (context);
+
+ return node;
+ } while (0);
+
+ g_warning ("Parse %s failed: %s", filename, error->message);
+ g_error_free (error);
+ g_markup_parse_context_free (context);
+ return NULL;
+}
+
+XMLNode *
+xml_parse_buffer (const gchar *buffer, GError **error) {
+ gboolean retval;
+
+ GMarkupParseContext *context;
+ XMLNode *node;
+
+ static const GMarkupParser root_parser = {
+ _start_root_element_cb,
+ _end_element_cb,
+ _text_cb,
+ 0,
+ 0,
+ };
+
+ context = g_markup_parse_context_new (&root_parser, 0, &node, 0);
+
+ do {
+ retval = g_markup_parse_context_parse (context, buffer, strlen (buffer), error);
+ if (!retval)
+ break;
+
+ retval = g_markup_parse_context_end_parse (context, error);
+ if (!retval)
+ break;
+ g_markup_parse_context_free (context);
+ return node;
+ } while (0);
+
+ g_markup_parse_context_free (context);
+ return NULL;
+}
+
+
+static void output_indent (int level, GString *output) {
+ gint i;
+ for (i = 0; i < level; i++) {
+ g_string_append (output, " ");
+ }
+}
+
+static void xml_output_indent (
+ const XMLNode *node,
+ int level,
+ GString *output
+) {
+ gchar **attrs;
+
+ output_indent (level, output);
+ g_string_append_printf (output, "<%s", node->name);
+
+ attrs = node->attributes;
+
+ while (attrs != NULL && *attrs != NULL) {
+ g_string_append_printf (output, " %s", *(attrs++));
+ g_string_append_printf (output, "=\"%s\"", *(attrs++));
+ }
+
+ if (node->sub_nodes != NULL) {
+ GList *sub_node;
+ g_string_append (output, ">\n");
+
+ for (sub_node = node->sub_nodes; sub_node != NULL; sub_node = sub_node->next) {
+ xml_output_indent (sub_node->data, level + 1, output);
+ }
+ output_indent (level, output);
+ g_string_append_printf (output, "</%s>\n",node->name);
+ } else if (node->text != NULL) {
+ gchar *text = g_markup_escape_text (node->text, -1);
+ g_string_append_printf (output, ">%s</%s>\n", text, node->name);
+ g_free (text);
+ } else {
+ g_string_append (output, "/>\n");
+ }
+}
+
+void xml_output (const XMLNode *node, GString *output) {
+ xml_output_indent (node, 0, output);
+}
+
diff --git a/saedit/xml.h b/saedit/xml.h
new file mode 100644
index 0000000..2622f17
--- /dev/null
+++ b/saedit/xml.h
@@ -0,0 +1,70 @@
+#ifndef XML_H
+#define XML_H
+
+#include <stdio.h>
+#include <glib.h>
+#include "common.h"
+
+typedef struct {
+ gchar *name;
+ gchar *text;
+ gchar **attributes;
+ gint line_no;
+ GList *sub_nodes;
+} XMLNode;
+
+
+XMLNode *
+xml_parse_file (
+ const gchar *name
+);
+
+XMLNode *
+xml_parse_buffer (
+ const gchar *buffer,
+ GError **error
+);
+
+void xml_free (XMLNode *node);
+void xml_output (
+ const XMLNode *node,
+ GString *output
+);
+
+gchar **
+xml_attr_new (
+ const gchar *name,
+ const gchar *value
+);
+
+gchar *
+xml_node_get_attr_value (
+ const XMLNode *node,
+ const gchar *attr_name
+);
+
+gint
+xml_node_get_int_attr_value (
+ const XMLNode *node,
+ const gchar *attr_name,
+ gint retval
+);
+
+gint
+xml_node_get_int_attr_value_limited (
+ const XMLNode *node,
+ const gchar *attr_name,
+ gint retval,
+ gint lower,
+ gint upper
+);
+
+
+gint xml_node_compare_with_name_func(gconstpointer a, gconstpointer b);
+
+/*
+gint xml_node_compare_with_action_node_by_imageset_name_func(gconstpointer a, gconstpointer b);
+gint xml_node_compare_with_attr_func(const XMLNode *node, const gchar **attr);
+*/
+
+#endif
diff --git a/saedit/xmlsetup.c b/saedit/xmlsetup.c
new file mode 100644
index 0000000..1036436
--- /dev/null
+++ b/saedit/xmlsetup.c
@@ -0,0 +1,165 @@
+#include "main.h"
+#include "xml.h"
+#include "xmlsetup.h"
+#include "config.h"
+#include "drawfuncs.h"
+
+GList *_xml_setup_layers = NULL;
+GList *_xml_setup_interactors = NULL;
+
+void
+xml_setup_setup (XMLNode *node) {
+ GList *l;
+
+ g_return_if_fail (g_strcmp0 (node->name, "saedit") == 0);
+
+ for (l = node->sub_nodes; l != NULL; l = l->next) {
+ XMLNode *current = (XMLNode *) l->data;
+
+ if (g_strcmp0 (current->name, "layer") == 0) {
+ SDALayer *layer = NULL;
+ gint z_index = -1;
+ gint offset_x, offset_y;
+ gchar *type = xml_node_get_attr_value (current, "type");
+
+ if (type == NULL) {
+ /* TODO: layer type is not specified */
+ continue;
+ }
+
+ if (g_strcmp0 (type, "repeater") == 0) {
+ Interactor **repeater;
+ SpriteContext *repeater_context;
+ XMLNode *sprite = NULL;
+ gchar *file;
+
+ file = xml_node_get_attr_value (current, "file");
+
+ if (file == NULL) {
+ /* TODO: file is not specified */
+ } else {
+ gchar *filename =
+ config_data_path_get_full_sprite_path (file);
+
+ sprite = xml_parse_file (filename);
+
+
+ g_free (file);
+ g_free (filename);
+ }
+
+ repeater_context = sprite_context_new (
+ config_keys_get_data_folder_path ()
+ );
+
+ sprite_context_add_sprite (repeater_context, sprite, 0);
+ repeater = (Interactor **) g_new0 (gpointer, 1);
+ *repeater = interactor_new (repeater_context);
+
+ layer = sda_layer_new (
+ interactor_sprite_layer_draw_func,
+ repeater
+ );
+
+ interactor_add_repeater (interactor, *repeater);
+
+ _xml_setup_interactors = g_list_append (
+ _xml_setup_interactors,
+ repeater
+ );
+ } else
+ if (g_strcmp0 (type, "background") == 0) {
+ GdkRGBA *rgba = (GdkRGBA *) g_new0 (GdkRGBA, 1);
+ gchar *color;
+
+ color = xml_node_get_attr_value (current, "color");
+
+ if (color != NULL && gdk_rgba_parse (rgba, color)) {
+ layer = sda_layer_new (
+ background_layer_draw_func,
+ rgba
+ );
+ } else {
+ /* TODO: color not specified or wrong */
+ }
+ } else {
+ /* TODO: unknown type of layer */
+ }
+
+ g_free (type);
+
+ if (layer == NULL) {
+ continue;
+ }
+
+ z_index = xml_node_get_int_attr_value (
+ current,
+ "zindex",
+ z_index
+ );
+
+ offset_x = xml_node_get_int_attr_value (
+ current,
+ "offsetX",
+ 0
+ );
+
+ offset_y = xml_node_get_int_attr_value (
+ current,
+ "offsetY",
+ 0
+ );
+
+ g_object_set (
+ G_OBJECT (layer),
+ "z-index", z_index,
+ "offset-x", offset_x,
+ "offset-y", offset_y,
+ NULL
+ );
+
+ sprite_drawing_area_add_layer (
+ SPRITE_DRAWING_AREA (d_area),
+ layer
+ );
+
+ _xml_setup_layers = g_list_append (
+ _xml_setup_layers,
+ layer
+ );
+ } else {
+ /* TODO: unknown tag in the setup */
+ }
+ }
+}
+
+void
+xml_setup_clear () {
+ GList *l;
+
+ for (l = _xml_setup_interactors; l != NULL; l = l->next) {
+ Interactor **intr = (Interactor **) l->data;
+ interactor_free (*intr);
+ }
+
+ for (l = _xml_setup_layers; l != NULL; l = l->next) {
+ SDALayer *layer = (SDALayer *) l->data;
+ gpointer user_data;
+
+ g_object_get (layer, "user-data", &user_data, NULL);
+ g_free (user_data);
+
+ sprite_drawing_area_remove_layer (
+ SPRITE_DRAWING_AREA (d_area),
+ layer
+ );
+
+ g_object_unref (layer);
+ }
+
+ g_list_free (_xml_setup_interactors);
+ g_list_free (_xml_setup_layers);
+
+ _xml_setup_interactors = NULL;
+ _xml_setup_layers = NULL;
+}
diff --git a/saedit/xmlsetup.h b/saedit/xmlsetup.h
new file mode 100644
index 0000000..37c9b65
--- /dev/null
+++ b/saedit/xmlsetup.h
@@ -0,0 +1,10 @@
+#ifndef _XML_SETUP_H_
+#define _XML_SETUP_H_
+
+void
+xml_setup_setup (XMLNode *node);
+
+void
+xml_setup_clear (void);
+
+#endif