diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/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.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General 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:
+
+    <program>  Copyright (C) <year>  <name of author>
+    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/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,6 @@
+module Main (main) where
+
+import Distribution.Simple
+
+main :: IO ()
+main = defaultMain
diff --git a/app-data/themes/gtk/default/app-specific.css b/app-data/themes/gtk/default/app-specific.css
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/app-specific.css
@@ -0,0 +1,10 @@
+.entry.error, .entry.error:focus {
+  color: #FF7777;
+  background-color: mix(@theme_bg_color, red, 0.1);
+  border-color: red;
+}
+
+.entry.error:selected {
+  color: #FFEE99;
+  background-color: mix(@theme_bg_color, yellow, 0.2);
+}
diff --git a/app-data/themes/gtk/default/assets/check-dark.svg b/app-data/themes/gtk/default/assets/check-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/check-dark.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="translate(-61.001 -787)">
+  <path id="path8913-6-7-1-5" style="block-progression:tb;text-indent:0;color:#eeeeec;text-transform:none;fill:#eeeeec" d="m72.938 790.94-4.938 4.94l-1.9375-1.9375-2.125 2.125 3 3 1.0625 1.0625 1.0625-1.0625 6-6-2.125-2.125z"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/check-insensitive-dark.svg b/app-data/themes/gtk/default/assets/check-insensitive-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/check-insensitive-dark.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="translate(-61.001 -787)">
+  <path id="path8913-6-7-1-5" style="opacity:0.5;block-progression:tb;text-indent:0;color:#eeeeec;text-transform:none;fill:#eeeeec" d="m72.938 790.94-4.938 4.94l-1.9375-1.9375-2.125 2.125 3 3 1.0625 1.0625 1.0625-1.0625 6-6-2.125-2.125z"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/check-insensitive.svg b/app-data/themes/gtk/default/assets/check-insensitive.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/check-insensitive.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="translate(-61.001 -787)">
+  <path id="path8913-6-7-1-5" style="opacity:0.5;block-progression:tb;text-indent:0;color:#2e3436;text-transform:none;fill:#2e3436" d="m72.938 790.94-4.938 4.94l-1.9375-1.9375-2.125 2.125 3 3 1.0625 1.0625 1.0625-1.0625 6-6-2.125-2.125z"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/check-selected.svg b/app-data/themes/gtk/default/assets/check-selected.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/check-selected.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="translate(-61.001 -787)">
+  <path id="path8913-6-7-1-5" style="block-progression:tb;text-indent:0;color:#ffffff;text-transform:none;fill:#ffffff" d="m72.938 790.94-4.938 4.94l-1.9375-1.9375-2.125 2.125 3 3 1.0625 1.0625 1.0625-1.0625 6-6-2.125-2.125z"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/check.svg b/app-data/themes/gtk/default/assets/check.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/check.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="translate(-61.001 -787)">
+  <path id="path8913-6-7-1-5" style="block-progression:tb;text-indent:0;color:#2e3436;text-transform:none;fill:#2e3436" d="m72.938 790.94-4.938 4.94l-1.9375-1.9375-2.125 2.125 3 3 1.0625 1.0625 1.0625-1.0625 6-6-2.125-2.125z"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/mix-dark.svg b/app-data/themes/gtk/default/assets/mix-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/mix-dark.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="translate(-40 -726)">
+  <rect id="rect31984" style="color:#eeeeec;fill:#eeeeec" height="2.0002" width="9.9996" y="733" x="43"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/mix-insensitive-dark.svg b/app-data/themes/gtk/default/assets/mix-insensitive-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/mix-insensitive-dark.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="translate(-40 -726)">
+  <rect id="rect31984" style="opacity:0.5;color:#eeeeec;fill:#eeeeec" height="2.0002" width="9.9996" y="733" x="43"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/mix-insensitive.svg b/app-data/themes/gtk/default/assets/mix-insensitive.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/mix-insensitive.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="translate(-40 -726)">
+  <rect id="rect31984" style="opacity:0.5;color:#2e3436;fill:#2e3436" height="2.0002" width="9.9996" y="733" x="43"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/mix-selected.svg b/app-data/themes/gtk/default/assets/mix-selected.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/mix-selected.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="translate(-40 -726)">
+  <rect id="rect31984" style="color:#ffffff;fill:#ffffff" height="2.0002" width="9.9996" y="733" x="43"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/mix.svg b/app-data/themes/gtk/default/assets/mix.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/mix.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="translate(-40 -726)">
+  <rect id="rect31984" style="color:#2e3436;fill:#2e3436" height="2.0002" width="9.9996" y="733" x="43"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/noise-texture-dark.png b/app-data/themes/gtk/default/assets/noise-texture-dark.png
new file mode 100644
Binary files /dev/null and b/app-data/themes/gtk/default/assets/noise-texture-dark.png differ
diff --git a/app-data/themes/gtk/default/assets/noise-texture.png b/app-data/themes/gtk/default/assets/noise-texture.png
new file mode 100644
Binary files /dev/null and b/app-data/themes/gtk/default/assets/noise-texture.png differ
diff --git a/app-data/themes/gtk/default/assets/pane-separator-grip-active.svg b/app-data/themes/gtk/default/assets/pane-separator-grip-active.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/pane-separator-grip-active.svg
@@ -0,0 +1,115 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="2"
+   height="22"
+   id="svg32934"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="pane-separator-grip.svg">
+  <defs
+     id="defs32936" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="32.454545"
+     inkscape:cx="1"
+     inkscape:cy="11"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="917"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3836"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata32939">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,6)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3838"
+       sodipodi:cx="1"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 2,1 A 1,1 0 1 1 0,1 1,1 0 1 1 2,1 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3840"
+       sodipodi:cx="1"
+       sodipodi:cy="6"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 2,6 A 1,1 0 1 1 0,6 1,1 0 1 1 2,6 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3842"
+       sodipodi:cx="1"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3844"
+       sodipodi:cx="1"
+       sodipodi:cy="16"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,16 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3846"
+       sodipodi:cx="1"
+       sodipodi:cy="21"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,21 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,-6)" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/pane-separator-grip-dark.svg b/app-data/themes/gtk/default/assets/pane-separator-grip-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/pane-separator-grip-dark.svg
@@ -0,0 +1,115 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="2"
+   height="22"
+   id="svg32934"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="pane-separator-grip.svg">
+  <defs
+     id="defs32936" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="32.454545"
+     inkscape:cx="1"
+     inkscape:cy="11"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="917"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3836"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata32939">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,6)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3838"
+       sodipodi:cx="1"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 2,1 A 1,1 0 1 1 0,1 1,1 0 1 1 2,1 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3840"
+       sodipodi:cx="1"
+       sodipodi:cy="6"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 2,6 A 1,1 0 1 1 0,6 1,1 0 1 1 2,6 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3842"
+       sodipodi:cx="1"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3844"
+       sodipodi:cx="1"
+       sodipodi:cy="16"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,16 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3846"
+       sodipodi:cx="1"
+       sodipodi:cy="21"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,21 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,-6)" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/pane-separator-grip-hover-dark.svg b/app-data/themes/gtk/default/assets/pane-separator-grip-hover-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/pane-separator-grip-hover-dark.svg
@@ -0,0 +1,115 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="2"
+   height="22"
+   id="svg32934"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="pane-separator-grip.svg">
+  <defs
+     id="defs32936" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="32.454545"
+     inkscape:cx="1"
+     inkscape:cy="11"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="917"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3836"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata32939">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,6)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3838"
+       sodipodi:cx="1"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 2,1 A 1,1 0 1 1 0,1 1,1 0 1 1 2,1 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3840"
+       sodipodi:cx="1"
+       sodipodi:cy="6"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 2,6 A 1,1 0 1 1 0,6 1,1 0 1 1 2,6 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3842"
+       sodipodi:cx="1"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3844"
+       sodipodi:cx="1"
+       sodipodi:cy="16"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,16 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3846"
+       sodipodi:cx="1"
+       sodipodi:cy="21"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,21 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,-6)" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/pane-separator-grip-hover.svg b/app-data/themes/gtk/default/assets/pane-separator-grip-hover.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/pane-separator-grip-hover.svg
@@ -0,0 +1,115 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="2"
+   height="22"
+   id="svg32934"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="pane-separator-grip.svg">
+  <defs
+     id="defs32936" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="32.454545"
+     inkscape:cx="1"
+     inkscape:cy="11"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="917"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3836"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata32939">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,6)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3838"
+       sodipodi:cx="1"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 2,1 A 1,1 0 1 1 0,1 1,1 0 1 1 2,1 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3840"
+       sodipodi:cx="1"
+       sodipodi:cy="6"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 2,6 A 1,1 0 1 1 0,6 1,1 0 1 1 2,6 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3842"
+       sodipodi:cx="1"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3844"
+       sodipodi:cx="1"
+       sodipodi:cy="16"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,16 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3846"
+       sodipodi:cx="1"
+       sodipodi:cy="21"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,21 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,-6)" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/pane-separator-grip-vertical-active.svg b/app-data/themes/gtk/default/assets/pane-separator-grip-vertical-active.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/pane-separator-grip-vertical-active.svg
@@ -0,0 +1,115 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="2"
+   id="svg32934"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="pane-separator-grip-vertical.svg">
+  <defs
+     id="defs32936" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="60.954545"
+     inkscape:cx="11"
+     inkscape:cy="1"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="917"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3757"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata32939">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,-14)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3759"
+       sodipodi:cx="1"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 2,1 A 1,1 0 1 1 0,1 1,1 0 1 1 2,1 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3761"
+       sodipodi:cx="6"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 7,1 A 1,1 0 1 1 5,1 1,1 0 1 1 7,1 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3763"
+       sodipodi:cx="11"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3765"
+       sodipodi:cx="16"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 17,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3767"
+       sodipodi:cx="21"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 22,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,14)" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/pane-separator-grip-vertical-dark.svg b/app-data/themes/gtk/default/assets/pane-separator-grip-vertical-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/pane-separator-grip-vertical-dark.svg
@@ -0,0 +1,115 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="2"
+   id="svg32934"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="pane-separator-grip-vertical.svg">
+  <defs
+     id="defs32936" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="60.954545"
+     inkscape:cx="11"
+     inkscape:cy="1"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="917"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3757"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata32939">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,-14)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3759"
+       sodipodi:cx="1"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 2,1 A 1,1 0 1 1 0,1 1,1 0 1 1 2,1 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3761"
+       sodipodi:cx="6"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 7,1 A 1,1 0 1 1 5,1 1,1 0 1 1 7,1 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3763"
+       sodipodi:cx="11"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3765"
+       sodipodi:cx="16"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 17,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3767"
+       sodipodi:cx="21"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 22,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,14)" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/pane-separator-grip-vertical-hover-dark.svg b/app-data/themes/gtk/default/assets/pane-separator-grip-vertical-hover-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/pane-separator-grip-vertical-hover-dark.svg
@@ -0,0 +1,115 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="2"
+   id="svg32934"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="pane-separator-grip-vertical.svg">
+  <defs
+     id="defs32936" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="60.954545"
+     inkscape:cx="11"
+     inkscape:cy="1"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="917"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3757"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata32939">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,-14)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3759"
+       sodipodi:cx="1"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 2,1 A 1,1 0 1 1 0,1 1,1 0 1 1 2,1 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3761"
+       sodipodi:cx="6"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 7,1 A 1,1 0 1 1 5,1 1,1 0 1 1 7,1 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3763"
+       sodipodi:cx="11"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3765"
+       sodipodi:cx="16"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 17,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3767"
+       sodipodi:cx="21"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 22,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,14)" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/pane-separator-grip-vertical-hover.svg b/app-data/themes/gtk/default/assets/pane-separator-grip-vertical-hover.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/pane-separator-grip-vertical-hover.svg
@@ -0,0 +1,115 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="2"
+   id="svg32934"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="pane-separator-grip-vertical.svg">
+  <defs
+     id="defs32936" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="60.954545"
+     inkscape:cx="11"
+     inkscape:cy="1"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="917"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3757"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata32939">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,-14)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3759"
+       sodipodi:cx="1"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 2,1 A 1,1 0 1 1 0,1 1,1 0 1 1 2,1 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3761"
+       sodipodi:cx="6"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 7,1 A 1,1 0 1 1 5,1 1,1 0 1 1 7,1 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3763"
+       sodipodi:cx="11"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3765"
+       sodipodi:cx="16"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 17,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3767"
+       sodipodi:cx="21"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 22,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,14)" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/pane-separator-grip-vertical.svg b/app-data/themes/gtk/default/assets/pane-separator-grip-vertical.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/pane-separator-grip-vertical.svg
@@ -0,0 +1,115 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="22"
+   height="2"
+   id="svg32934"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="pane-separator-grip-vertical.svg">
+  <defs
+     id="defs32936" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="60.954545"
+     inkscape:cx="11"
+     inkscape:cy="1"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="917"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3757"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata32939">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,-14)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3759"
+       sodipodi:cx="1"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 2,1 A 1,1 0 1 1 0,1 1,1 0 1 1 2,1 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3761"
+       sodipodi:cx="6"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 7,1 A 1,1 0 1 1 5,1 1,1 0 1 1 7,1 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3763"
+       sodipodi:cx="11"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3765"
+       sodipodi:cx="16"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 17,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,14)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3767"
+       sodipodi:cx="21"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 22,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,14)" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/pane-separator-grip.svg b/app-data/themes/gtk/default/assets/pane-separator-grip.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/pane-separator-grip.svg
@@ -0,0 +1,115 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="2"
+   height="22"
+   id="svg32934"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="pane-separator-grip.svg">
+  <defs
+     id="defs32936" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="32.454545"
+     inkscape:cx="1"
+     inkscape:cy="11"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="917"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3836"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata32939">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,6)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3838"
+       sodipodi:cx="1"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 2,1 A 1,1 0 1 1 0,1 1,1 0 1 1 2,1 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3840"
+       sodipodi:cx="1"
+       sodipodi:cy="6"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 2,6 A 1,1 0 1 1 0,6 1,1 0 1 1 2,6 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3842"
+       sodipodi:cx="1"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3844"
+       sodipodi:cx="1"
+       sodipodi:cy="16"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,16 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,-6)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3846"
+       sodipodi:cx="1"
+       sodipodi:cy="21"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,21 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,-6)" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/radio-dark.svg b/app-data/themes/gtk/default/assets/radio-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/radio-dark.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="matrix(0.88734967,0,0,0.54841228,-578.5098,-131.88177)">
+  <path id="path4646-0" style="color:#eeeeec;text-indent:0pt;text-transform:none;fill:#eeeeec" d="m 665.47588,255.0668 a 4.5078058,7.2937827 0 1 1 -9.01561,0 4.5078058,7.2937827 0 1 1 9.01561,0 z"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/radio-hover-dark.svg b/app-data/themes/gtk/default/assets/radio-hover-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/radio-hover-dark.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="matrix(0.88734967,0,0,0.54841228,-578.5098,-131.88177)">
+  <path id="path4646-0" style="color:#babdb6;text-indent:0pt;text-transform:none;fill:#babdb6" d="m 665.47588,255.0668 a 4.5078058,7.2937827 0 1 1 -9.01561,0 4.5078058,7.2937827 0 1 1 9.01561,0 z"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/radio-hover.svg b/app-data/themes/gtk/default/assets/radio-hover.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/radio-hover.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="matrix(0.88734967,0,0,0.54841228,-578.5098,-131.88177)">
+  <path id="path4646-0" style="color:#888a85;text-indent:0pt;text-transform:none;fill:#888a85" d="m 665.47588,255.0668 a 4.5078058,7.2937827 0 1 1 -9.01561,0 4.5078058,7.2937827 0 1 1 9.01561,0 z"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/radio-insensitive-dark.svg b/app-data/themes/gtk/default/assets/radio-insensitive-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/radio-insensitive-dark.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="matrix(0.88734967,0,0,0.54841228,-578.5098,-131.88177)">
+  <path id="path4646-0" style="opacity:0.5;color:#eeeeec;text-indent:0pt;text-transform:none;fill:#eeeeec" d="m 665.47588,255.0668 a 4.5078058,7.2937827 0 1 1 -9.01561,0 4.5078058,7.2937827 0 1 1 9.01561,0 z"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/radio-insensitive.svg b/app-data/themes/gtk/default/assets/radio-insensitive.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/radio-insensitive.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="matrix(0.88734967,0,0,0.54841228,-578.5098,-131.88177)">
+  <path id="path4646-0" style="opacity:0.5;color:#2e3436;text-indent:0pt;text-transform:none;fill:#2e3436" d="m 665.47588,255.0668 a 4.5078058,7.2937827 0 1 1 -9.01561,0 4.5078058,7.2937827 0 1 1 9.01561,0 z"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/radio-selected-hover.svg b/app-data/themes/gtk/default/assets/radio-selected-hover.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/radio-selected-hover.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="matrix(0.88734967,0,0,0.54841228,-578.5098,-131.88177)">
+  <path id="path4646-0" style="opacity:0.5;color:#ffffff;text-indent:0pt;text-transform:none;fill:#ffffff" d="m 665.47588,255.0668 a 4.5078058,7.2937827 0 1 1 -9.01561,0 4.5078058,7.2937827 0 1 1 9.01561,0 z"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/radio-selected.svg b/app-data/themes/gtk/default/assets/radio-selected.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/radio-selected.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="matrix(0.88734967,0,0,0.54841228,-578.5098,-131.88177)">
+  <path id="path4646-0" style="color:#ffffff;text-indent:0pt;text-transform:none;fill:#ffffff" d="m 665.47588,255.0668 a 4.5078058,7.2937827 0 1 1 -9.01561,0 4.5078058,7.2937827 0 1 1 9.01561,0 z"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/radio.svg b/app-data/themes/gtk/default/assets/radio.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/radio.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg7384" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title9167">Gnome Symbolic Icon Theme</title>
+ <metadata id="metadata90">
+  <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>Gnome Symbolic Icon Theme</dc:title>
+   </cc:Work>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer12" transform="matrix(0.88734967,0,0,0.54841228,-578.5098,-131.88177)">
+  <path id="path4646-0" style="color:#2e3436;text-indent:0pt;text-transform:none;fill:#2e3436" d="m 665.47588,255.0668 a 4.5078058,7.2937827 0 1 1 -9.01561,0 4.5078058,7.2937827 0 1 1 9.01561,0 z"/>
+ </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/resize-grip-active.svg b/app-data/themes/gtk/default/assets/resize-grip-active.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/resize-grip-active.svg
@@ -0,0 +1,125 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="12"
+   height="12"
+   id="svg17653"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="resize-grip.svg">
+  <defs
+     id="defs17655" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="59.5"
+     inkscape:cx="6"
+     inkscape:cy="6"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="917"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3758"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata17658">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,-4)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3776"
+       sodipodi:cx="11"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3778"
+       sodipodi:cx="11"
+       sodipodi:cy="6"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,6 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3780"
+       sodipodi:cx="11"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3782"
+       sodipodi:cx="6"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 7,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3784"
+       sodipodi:cx="1"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#4e9a06;fill-opacity:1;stroke:none"
+       id="path3786"
+       sodipodi:cx="6"
+       sodipodi:cy="6"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 7,6 A 1,1 0 1 1 5,6 1,1 0 1 1 7,6 z"
+       transform="translate(0,4)" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/resize-grip-dark.svg b/app-data/themes/gtk/default/assets/resize-grip-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/resize-grip-dark.svg
@@ -0,0 +1,125 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="12"
+   height="12"
+   id="svg17653"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="resize-grip.svg">
+  <defs
+     id="defs17655" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="59.5"
+     inkscape:cx="6"
+     inkscape:cy="6"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="917"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3758"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata17658">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,-4)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3776"
+       sodipodi:cx="11"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3778"
+       sodipodi:cx="11"
+       sodipodi:cy="6"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,6 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3780"
+       sodipodi:cx="11"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3782"
+       sodipodi:cx="6"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 7,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3784"
+       sodipodi:cx="1"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#babdb6;fill-opacity:1;stroke:none"
+       id="path3786"
+       sodipodi:cx="6"
+       sodipodi:cy="6"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 7,6 A 1,1 0 1 1 5,6 1,1 0 1 1 7,6 z"
+       transform="translate(0,4)" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/resize-grip-hover-dark.svg b/app-data/themes/gtk/default/assets/resize-grip-hover-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/resize-grip-hover-dark.svg
@@ -0,0 +1,125 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="12"
+   height="12"
+   id="svg17653"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="resize-grip.svg">
+  <defs
+     id="defs17655" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="59.5"
+     inkscape:cx="6"
+     inkscape:cy="6"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="917"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3758"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata17658">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,-4)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3776"
+       sodipodi:cx="11"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3778"
+       sodipodi:cx="11"
+       sodipodi:cy="6"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,6 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3780"
+       sodipodi:cx="11"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3782"
+       sodipodi:cx="6"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 7,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3784"
+       sodipodi:cx="1"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       id="path3786"
+       sodipodi:cx="6"
+       sodipodi:cy="6"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 7,6 A 1,1 0 1 1 5,6 1,1 0 1 1 7,6 z"
+       transform="translate(0,4)" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/resize-grip-hover.svg b/app-data/themes/gtk/default/assets/resize-grip-hover.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/resize-grip-hover.svg
@@ -0,0 +1,125 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="12"
+   height="12"
+   id="svg17653"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="resize-grip.svg">
+  <defs
+     id="defs17655" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="59.5"
+     inkscape:cx="6"
+     inkscape:cy="6"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="917"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3758"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata17658">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,-4)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3776"
+       sodipodi:cx="11"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3778"
+       sodipodi:cx="11"
+       sodipodi:cy="6"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,6 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3780"
+       sodipodi:cx="11"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3782"
+       sodipodi:cx="6"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 7,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3784"
+       sodipodi:cx="1"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#2e3436;fill-opacity:1;stroke:none"
+       id="path3786"
+       sodipodi:cx="6"
+       sodipodi:cy="6"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 7,6 A 1,1 0 1 1 5,6 1,1 0 1 1 7,6 z"
+       transform="translate(0,4)" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/resize-grip.svg b/app-data/themes/gtk/default/assets/resize-grip.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/resize-grip.svg
@@ -0,0 +1,125 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="12"
+   height="12"
+   id="svg17653"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="resize-grip.svg">
+  <defs
+     id="defs17655" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="59.5"
+     inkscape:cx="6"
+     inkscape:cy="6"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1920"
+     inkscape:window-height="917"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3758"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata17658">
+    <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 />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,-4)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3776"
+       sodipodi:cx="11"
+       sodipodi:cy="1"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,1 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3778"
+       sodipodi:cx="11"
+       sodipodi:cy="6"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,6 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3780"
+       sodipodi:cx="11"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 12,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3782"
+       sodipodi:cx="6"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 7,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3784"
+       sodipodi:cx="1"
+       sodipodi:cy="11"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="m 2,11 a 1,1 0 1 1 -2,0 1,1 0 1 1 2,0 z"
+       transform="translate(0,4)" />
+    <path
+       sodipodi:type="arc"
+       style="fill:#888a85;fill-opacity:1;stroke:none"
+       id="path3786"
+       sodipodi:cx="6"
+       sodipodi:cy="6"
+       sodipodi:rx="1"
+       sodipodi:ry="1"
+       d="M 7,6 A 1,1 0 1 1 5,6 1,1 0 1 1 7,6 z"
+       transform="translate(0,4)" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-down-active-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-down-active-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-down-active-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-left.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#555753;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#373c3d;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="1"
+       x2="12"
+       y2="23"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#63A129;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,12 12,-12 z m 0,0 c 0,-6.6274 5.3726,-12 12,-12 6.6274,0 12,5.3726 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,11 11,-11 z m 0,0 c 0,-6.0751 4.9249,-11 11,-11 6.0751,0 11,4.9249 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-down-active.svg b/app-data/themes/gtk/default/assets/scale-slider-down-active.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-down-active.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-left.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#e1e3de;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="1"
+       x2="12"
+       y2="23"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#59971F;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,12 12,-12 z m 0,0 c 0,-6.6274 5.3726,-12 12,-12 6.6274,0 12,5.3726 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,11 11,-11 z m 0,0 c 0,-6.0751 4.9249,-11 11,-11 6.0751,0 11,4.9249 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-down-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-down-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-down-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-left.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#4b4e4c;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#2c2f2f;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="1"
+       x2="12"
+       y2="23"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#0B0D0D;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,12 12,-12 z m 0,0 c 0,-6.6274 5.3726,-12 12,-12 6.6274,0 12,5.3726 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,11 11,-11 z m 0,0 c 0,-6.0751 4.9249,-11 11,-11 6.0751,0 11,4.9249 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-down-fine-tune-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-down-fine-tune-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-down-fine-tune-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-left.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#536443;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#3B4F32;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="1"
+       x2="12"
+       y2="23"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#63A129;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,12 12,-12 z m 0,0 c 0,-6.6274 5.3726,-12 12,-12 6.6274,0 12,5.3726 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,11 11,-11 z m 0,0 c 0,-6.0751 4.9249,-11 11,-11 6.0751,0 11,4.9249 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-down-fine-tune.svg b/app-data/themes/gtk/default/assets/scale-slider-down-fine-tune.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-down-fine-tune.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-left.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#DCEBCE;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#C4D5B3;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="1"
+       x2="12"
+       y2="23"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#59971F;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,12 12,-12 z m 0,0 c 0,-6.6274 5.3726,-12 12,-12 6.6274,0 12,5.3726 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,11 11,-11 z m 0,0 c 0,-6.0751 4.9249,-11 11,-11 6.0751,0 11,4.9249 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-down-hover-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-down-hover-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-down-hover-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-left.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#555753;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#373c3d;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="1"
+       x2="12"
+       y2="23"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#0B0D0D;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,12 12,-12 z m 0,0 c 0,-6.6274 5.3726,-12 12,-12 6.6274,0 12,5.3726 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,11 11,-11 z m 0,0 c 0,-6.0751 4.9249,-11 11,-11 6.0751,0 11,4.9249 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-down-hover.svg b/app-data/themes/gtk/default/assets/scale-slider-down-hover.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-down-hover.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-left.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#e1e3de;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="1"
+       x2="12"
+       y2="23"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#A1A49E;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,12 12,-12 z m 0,0 c 0,-6.6274 5.3726,-12 12,-12 6.6274,0 12,5.3726 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,11 11,-11 z m 0,0 c 0,-6.0751 4.9249,-11 11,-11 6.0751,0 11,4.9249 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-down-insensitive-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-down-insensitive-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-down-insensitive-dark.svg
@@ -0,0 +1,73 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-left.svg">
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#0B0D0D;fill-opacity:0.5;stroke:none"
+       d="m 0,1040.3622 12,12 12,-12 z m 0,0 c 0,-6.6274 5.3726,-12 12,-12 6.6274,0 12,5.3726 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       id="path3765"
+       style="fill:#414544;fill-opacity:1;stroke:none"
+       d="m 1,1040.3622 11,11 11,-11 z m 0,0 c 0,-6.0751 4.9249,-11 11,-11 6.0751,0 11,4.9249 11,11"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-down-insensitive.svg b/app-data/themes/gtk/default/assets/scale-slider-down-insensitive.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-down-insensitive.svg
@@ -0,0 +1,73 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-left.svg">
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#A1A49E;fill-opacity:0.5;stroke:none"
+       d="m 0,1040.3622 12,12 12,-12 z m 0,0 c 0,-6.6274 5.3726,-12 12,-12 6.6274,0 12,5.3726 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       id="path3765"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       d="m 1,1040.3622 11,11 11,-11 z m 0,0 c 0,-6.0751 4.9249,-11 11,-11 6.0751,0 11,4.9249 11,11"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-down.svg b/app-data/themes/gtk/default/assets/scale-slider-down.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-down.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-left.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#f7f7f6;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#d4d6d1;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="1"
+       x2="12"
+       y2="23"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="translate(0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#A1A49E;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,12 12,-12 z m 0,0 c 0,-6.6274 5.3726,-12 12,-12 6.6274,0 12,5.3726 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,11 11,-11 z m 0,0 c 0,-6.0751 4.9249,-11 11,-11 6.0751,0 11,4.9249 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-left-active-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-left-active-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-left-active-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-right.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#555753;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#373c3d;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,-1,0,24,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#63A129;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 -12,12 12,12 z m 0,0 c 6.6274,0 12,5.3726 12,12 0,6.6274 -5.3726,12 -12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 -11,11 11,11 z m 0,0 c 6.0751,0 11,4.9249 11,11 0,6.0751 -4.9249,11 -11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-left-active.svg b/app-data/themes/gtk/default/assets/scale-slider-left-active.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-left-active.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-right.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#e1e3de;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,-1,0,24,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#59971F;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 -12,12 12,12 z m 0,0 c 6.6274,0 12,5.3726 12,12 0,6.6274 -5.3726,12 -12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 -11,11 11,11 z m 0,0 c 6.0751,0 11,4.9249 11,11 0,6.0751 -4.9249,11 -11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-left-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-left-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-left-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-right.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#4b4e4c;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#2c2f2f;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,-1,0,24,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#0B0D0D;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 -12,12 12,12 z m 0,0 c 6.6274,0 12,5.3726 12,12 0,6.6274 -5.3726,12 -12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 -11,11 11,11 z m 0,0 c 6.0751,0 11,4.9249 11,11 0,6.0751 -4.9249,11 -11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-left-fine-tune-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-left-fine-tune-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-left-fine-tune-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-right.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#536443;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#3B4F32;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,-1,0,24,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#63A129;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 -12,12 12,12 z m 0,0 c 6.6274,0 12,5.3726 12,12 0,6.6274 -5.3726,12 -12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 -11,11 11,11 z m 0,0 c 6.0751,0 11,4.9249 11,11 0,6.0751 -4.9249,11 -11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-left-fine-tune.svg b/app-data/themes/gtk/default/assets/scale-slider-left-fine-tune.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-left-fine-tune.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-right.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#DCEBCE;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#C4D5B3;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,-1,0,24,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#59971F;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 -12,12 12,12 z m 0,0 c 6.6274,0 12,5.3726 12,12 0,6.6274 -5.3726,12 -12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 -11,11 11,11 z m 0,0 c 6.0751,0 11,4.9249 11,11 0,6.0751 -4.9249,11 -11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-left-hover-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-left-hover-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-left-hover-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-right.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#555753;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#373c3d;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,-1,0,24,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#0B0D0D;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 -12,12 12,12 z m 0,0 c 6.6274,0 12,5.3726 12,12 0,6.6274 -5.3726,12 -12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 -11,11 11,11 z m 0,0 c 6.0751,0 11,4.9249 11,11 0,6.0751 -4.9249,11 -11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-left-hover.svg b/app-data/themes/gtk/default/assets/scale-slider-left-hover.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-left-hover.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-right.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#e1e3de;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,-1,0,24,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#A1A49E;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 -12,12 12,12 z m 0,0 c 6.6274,0 12,5.3726 12,12 0,6.6274 -5.3726,12 -12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 -11,11 11,11 z m 0,0 c 6.0751,0 11,4.9249 11,11 0,6.0751 -4.9249,11 -11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-left-insensitive-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-left-insensitive-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-left-insensitive-dark.svg
@@ -0,0 +1,73 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-right.svg">
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#0B0D0D;fill-opacity:0.5;stroke:none"
+       d="m 12,1028.3622 -12,12 12,12 z m 0,0 c 6.6274,0 12,5.3726 12,12 0,6.6274 -5.3726,12 -12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       id="path3765"
+       style="fill:#414544;fill-opacity:1;stroke:none"
+       d="m 12,1029.3622 -11,11 11,11 z m 0,0 c 6.0751,0 11,4.9249 11,11 0,6.0751 -4.9249,11 -11,11"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-left-insensitive.svg b/app-data/themes/gtk/default/assets/scale-slider-left-insensitive.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-left-insensitive.svg
@@ -0,0 +1,73 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-right.svg">
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#A1A49E;fill-opacity:0.5;stroke:none"
+       d="m 12,1028.3622 -12,12 12,12 z m 0,0 c 6.6274,0 12,5.3726 12,12 0,6.6274 -5.3726,12 -12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       id="path3765"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       d="m 12,1029.3622 -11,11 11,11 z m 0,0 c 6.0751,0 11,4.9249 11,11 0,6.0751 -4.9249,11 -11,11"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-left.svg b/app-data/themes/gtk/default/assets/scale-slider-left.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-left.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-right.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#f7f7f6;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#d4d6d1;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,-1,0,24,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#A1A49E;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 -12,12 12,12 z m 0,0 c 6.6274,0 12,5.3726 12,12 0,6.6274 -5.3726,12 -12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 -11,11 11,11 z m 0,0 c 6.0751,0 11,4.9249 11,11 0,6.0751 -4.9249,11 -11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-right-active-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-right-active-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-right-active-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-up.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#555753;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#373c3d;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,1,0,0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#63A129;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 12,12 -12,12 z m 0,0 c -6.6274,0 -12,5.3726 -12,12 0,6.6274 5.3726,12 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 11,11 -11,11 z m 0,0 c -6.0751,0 -11,4.9249 -11,11 0,6.0751 4.9249,11 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-right-active.svg b/app-data/themes/gtk/default/assets/scale-slider-right-active.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-right-active.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-up.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#e1e3de;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,1,0,0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#59971F;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 12,12 -12,12 z m 0,0 c -6.6274,0 -12,5.3726 -12,12 0,6.6274 5.3726,12 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 11,11 -11,11 z m 0,0 c -6.0751,0 -11,4.9249 -11,11 0,6.0751 4.9249,11 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-right-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-right-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-right-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-up.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#4b4e4c;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#2c2f2f;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,1,0,0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#0B0D0D;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 12,12 -12,12 z m 0,0 c -6.6274,0 -12,5.3726 -12,12 0,6.6274 5.3726,12 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 11,11 -11,11 z m 0,0 c -6.0751,0 -11,4.9249 -11,11 0,6.0751 4.9249,11 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-right-fine-tune-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-right-fine-tune-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-right-fine-tune-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-up.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#536443;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#3B4F32;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,1,0,0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#63A129;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 12,12 -12,12 z m 0,0 c -6.6274,0 -12,5.3726 -12,12 0,6.6274 5.3726,12 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 11,11 -11,11 z m 0,0 c -6.0751,0 -11,4.9249 -11,11 0,6.0751 4.9249,11 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-right-fine-tune.svg b/app-data/themes/gtk/default/assets/scale-slider-right-fine-tune.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-right-fine-tune.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-up.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#DCEBCE;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#C4D5B3;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,1,0,0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#59971F;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 12,12 -12,12 z m 0,0 c -6.6274,0 -12,5.3726 -12,12 0,6.6274 5.3726,12 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 11,11 -11,11 z m 0,0 c -6.0751,0 -11,4.9249 -11,11 0,6.0751 4.9249,11 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-right-hover-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-right-hover-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-right-hover-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-up.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#555753;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#373c3d;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,1,0,0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#0B0D0D;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 12,12 -12,12 z m 0,0 c -6.6274,0 -12,5.3726 -12,12 0,6.6274 5.3726,12 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 11,11 -11,11 z m 0,0 c -6.0751,0 -11,4.9249 -11,11 0,6.0751 4.9249,11 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-right-hover.svg b/app-data/themes/gtk/default/assets/scale-slider-right-hover.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-right-hover.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-up.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#e1e3de;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,1,0,0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#A1A49E;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 12,12 -12,12 z m 0,0 c -6.6274,0 -12,5.3726 -12,12 0,6.6274 5.3726,12 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 11,11 -11,11 z m 0,0 c -6.0751,0 -11,4.9249 -11,11 0,6.0751 4.9249,11 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-right-insensitive-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-right-insensitive-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-right-insensitive-dark.svg
@@ -0,0 +1,73 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-up.svg">
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#0B0D0D;fill-opacity:0.5;stroke:none"
+       d="m 12,1028.3622 12,12 -12,12 z m 0,0 c -6.6274,0 -12,5.3726 -12,12 0,6.6274 5.3726,12 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       id="path3765"
+       style="fill:#414544;fill-opacity:1;stroke:none"
+       d="m 12,1029.3622 11,11 -11,11 z m 0,0 c -6.0751,0 -11,4.9249 -11,11 0,6.0751 4.9249,11 11,11"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-right-insensitive.svg b/app-data/themes/gtk/default/assets/scale-slider-right-insensitive.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-right-insensitive.svg
@@ -0,0 +1,73 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-up.svg">
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#A1A49E;fill-opacity:0.5;stroke:none"
+       d="m 12,1028.3622 12,12 -12,12 z m 0,0 c -6.6274,0 -12,5.3726 -12,12 0,6.6274 5.3726,12 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       id="path3765"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       d="m 12,1029.3622 11,11 -11,11 z m 0,0 c -6.0751,0 -11,4.9249 -11,11 0,6.0751 4.9249,11 11,11"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-right.svg b/app-data/themes/gtk/default/assets/scale-slider-right.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-right.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-up.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#f7f7f6;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#d4d6d1;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="1"
+       y1="12"
+       x2="23"
+       y2="12"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,1,1,0,0,1028.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#A1A49E;fill-opacity:1;stroke:none"
+       d="m 12,1028.3622 12,12 -12,12 z m 0,0 c -6.6274,0 -12,5.3726 -12,12 0,6.6274 5.3726,12 12,12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 12,1029.3622 11,11 -11,11 z m 0,0 c -6.0751,0 -11,4.9249 -11,11 0,6.0751 4.9249,11 11,11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-up-active-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-up-active-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-up-active-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-down.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#555753;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#373c3d;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="23"
+       x2="12"
+       y2="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,-1,0,1052.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#63A129;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,-12 12,12 z m 0,0 c 3.0757981e-7,6.6274 5.3725836,12 12.000001,12 6.627416,0 11.999999,-5.3726 11.999999,-12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,-11 11,11 z m 0,0 c 3e-7,6.0751 4.9248683,11 11.000001,11 6.075132,0 10.999999,-4.9249 10.999999,-11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-up-active.svg b/app-data/themes/gtk/default/assets/scale-slider-up-active.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-up-active.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-down.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#e1e3de;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="23"
+       x2="12"
+       y2="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,-1,0,1052.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#59971F;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,-12 12,12 z m 0,0 c 3.0757981e-7,6.6274 5.3725836,12 12.000001,12 6.627416,0 11.999999,-5.3726 11.999999,-12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,-11 11,11 z m 0,0 c 3e-7,6.0751 4.9248683,11 11.000001,11 6.075132,0 10.999999,-4.9249 10.999999,-11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-up-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-up-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-up-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-down.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#4b4e4c;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#2c2f2f;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="23"
+       x2="12"
+       y2="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,-1,0,1052.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#0B0D0D;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,-12 12,12 z m 0,0 c 3.0757981e-7,6.6274 5.3725836,12 12.000001,12 6.627416,0 11.999999,-5.3726 11.999999,-12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,-11 11,11 z m 0,0 c 3e-7,6.0751 4.9248683,11 11.000001,11 6.075132,0 10.999999,-4.9249 10.999999,-11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-up-fine-tune-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-up-fine-tune-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-up-fine-tune-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-down.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#536443;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#3B4F32;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="23"
+       x2="12"
+       y2="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,-1,0,1052.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#63A129;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,-12 12,12 z m 0,0 c 3.0757981e-7,6.6274 5.3725836,12 12.000001,12 6.627416,0 11.999999,-5.3726 11.999999,-12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,-11 11,11 z m 0,0 c 3e-7,6.0751 4.9248683,11 11.000001,11 6.075132,0 10.999999,-4.9249 10.999999,-11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-up-fine-tune.svg b/app-data/themes/gtk/default/assets/scale-slider-up-fine-tune.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-up-fine-tune.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-down.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#DCEBCE;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#C4D5B3;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="23"
+       x2="12"
+       y2="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,-1,0,1052.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#59971F;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,-12 12,12 z m 0,0 c 3.0757981e-7,6.6274 5.3725836,12 12.000001,12 6.627416,0 11.999999,-5.3726 11.999999,-12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,-11 11,11 z m 0,0 c 3e-7,6.0751 4.9248683,11 11.000001,11 6.075132,0 10.999999,-4.9249 10.999999,-11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-up-hover-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-up-hover-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-up-hover-dark.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-down.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#555753;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#373c3d;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="23"
+       x2="12"
+       y2="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,-1,0,1052.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#0B0D0D;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,-12 12,12 z m 0,0 c 3.0757981e-7,6.6274 5.3725836,12 12.000001,12 6.627416,0 11.999999,-5.3726 11.999999,-12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,-11 11,11 z m 0,0 c 3e-7,6.0751 4.9248683,11 11.000001,11 6.075132,0 10.999999,-4.9249 10.999999,-11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-up-hover.svg b/app-data/themes/gtk/default/assets/scale-slider-up-hover.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-up-hover.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-down.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#e1e3de;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="23"
+       x2="12"
+       y2="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,-1,0,1052.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#A1A49E;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,-12 12,12 z m 0,0 c 3.0757981e-7,6.6274 5.3725836,12 12.000001,12 6.627416,0 11.999999,-5.3726 11.999999,-12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,-11 11,11 z m 0,0 c 3e-7,6.0751 4.9248683,11 11.000001,11 6.075132,0 10.999999,-4.9249 10.999999,-11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-up-insensitive-dark.svg b/app-data/themes/gtk/default/assets/scale-slider-up-insensitive-dark.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-up-insensitive-dark.svg
@@ -0,0 +1,73 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-down.svg">
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#0B0D0D;fill-opacity:0.5;stroke:none"
+       d="m 0,1040.3622 12,-12 12,12 z m 0,0 c 3.0757981e-7,6.6274 5.3725836,12 12.000001,12 6.627416,0 11.999999,-5.3726 11.999999,-12"
+       inkscape:connector-curvature="0" />
+    <path
+       id="path3765"
+       style="fill:#414544;fill-opacity:1;stroke:none"
+       d="m 1,1040.3622 11,-11 11,11 z m 0,0 c 3e-7,6.0751 4.9248683,11 11.000001,11 6.075132,0 10.999999,-4.9249 10.999999,-11"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-up-insensitive.svg b/app-data/themes/gtk/default/assets/scale-slider-up-insensitive.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-up-insensitive.svg
@@ -0,0 +1,73 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-down.svg">
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#A1A49E;fill-opacity:0.5;stroke:none"
+       d="m 0,1040.3622 12,-12 12,12 z m 0,0 c 3.0757981e-7,6.6274 5.3725836,12 12.000001,12 6.627416,0 11.999999,-5.3726 11.999999,-12"
+       inkscape:connector-curvature="0" />
+    <path
+       id="path3765"
+       style="fill:#eeeeec;fill-opacity:1;stroke:none"
+       d="m 1,1040.3622 11,-11 11,11 z m 0,0 c 3e-7,6.0751 4.9248683,11 11.000001,11 6.075132,0 10.999999,-4.9249 10.999999,-11"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/scale-slider-up.svg b/app-data/themes/gtk/default/assets/scale-slider-up.svg
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/assets/scale-slider-up.svg
@@ -0,0 +1,97 @@
+<?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="24"
+   height="24"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="scale-slider-down.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3771">
+      <stop
+         style="stop-color:#f7f7f6;stop-opacity:1;"
+         offset="0"
+         id="stop3773" />
+      <stop
+         style="stop-color:#d4d6d1;stop-opacity:1;"
+         offset="1"
+         id="stop3775" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3771"
+       id="linearGradient3777"
+       x1="12"
+       y1="23"
+       x2="12"
+       y2="1"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1,0,0,-1,0,1052.3622)" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="29.541667"
+     inkscape:cx="12"
+     inkscape:cy="12"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1920"
+     inkscape:window-height="912"
+     inkscape:window-x="0"
+     inkscape:window-y="32"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2985"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <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="レイヤー 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1028.3622)">
+    <path
+       id="path2989"
+       style="fill:#A1A49E;fill-opacity:1;stroke:none"
+       d="m 0,1040.3622 12,-12 12,12 z m 0,0 c 3.0757981e-7,6.6274 5.3725836,12 12.000001,12 6.627416,0 11.999999,-5.3726 11.999999,-12"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 1,1040.3622 11,-11 11,11 z m 0,0 c 3e-7,6.0751 4.9248683,11 11.000001,11 6.075132,0 10.999999,-4.9249 10.999999,-11"
+       style="fill:url(#linearGradient3777);fill-opacity:1;stroke:none"
+       id="path3769"
+       inkscape:connector-curvature="0" />
+  </g>
+</svg>
diff --git a/app-data/themes/gtk/default/assets/thumbnail-frame.png b/app-data/themes/gtk/default/assets/thumbnail-frame.png
new file mode 100644
Binary files /dev/null and b/app-data/themes/gtk/default/assets/thumbnail-frame.png differ
diff --git a/app-data/themes/gtk/default/gtk-common.css b/app-data/themes/gtk/default/gtk-common.css
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/gtk-common.css
@@ -0,0 +1,59 @@
+/* common color definitions to the light and dark variations */
+@define-color alt_base_color mix(@theme_bg_color, @theme_base_color, 0.5);
+@define-color alt_subdued_color mix(@theme_bg_color, @subdued_color, 0.5);
+
+@define-color alt_border_color mix(@theme_tooltip_bg_color, @border_color, 0.5);
+@define-color colored_border_color mix(@alt_border_color, shade(@theme_selected_bg_color, 0.5), 0.2);
+@define-color focused_border_color mix(@internal_element_color, @theme_selected_bg_color, 0.8);
+@define-color suggested_action_border_color mix(@alt_border_color, shade(@theme_selected_bg_color, 0.2), 0.2);
+@define-color destructive_action_border_color mix(@alt_border_color, shade(@error_color, 0.2), 0.2);
+@define-color insensitive_border_color alpha(@alt_border_color, 0.5);
+
+/* 
+@define-color insensitive_fg_color alpha(@theme_fg_color, 0.5);
+@define-color insensitive_internal_element_color alpha(@internal_element_color, 0.5);
+@define-color selected_internal_element_color alpha(@theme_selected_fg_color, 0.6);
+ */
+@define-color insensitive_fg_color mix(@theme_fg_color, @theme_bg_color, 0.5);
+@define-color insensitive_internal_element_color mix(@internal_element_color, @theme_bg_color, 0.5);
+@define-color selected_internal_element_color mix(@theme_selected_fg_color, @theme_selected_bg_color, 0.4);
+
+@define-color button_top_color mix(@active_element, @theme_base_color, 0.7);
+@define-color button_bottom_color mix(@active_element, @theme_bg_color, 0.7);
+@define-color button_top_hover_color mix(@active_element, @theme_base_color, 0.6);
+@define-color button_bottom_hover_color mix(@active_element, @theme_bg_color, 0.6);
+@define-color button_top_active_color mix(@active_element, @theme_base_color, 0.5);
+@define-color button_bottom_active_color  mix(@active_element, @theme_bg_color, 0.5);
+@define-color button_top_active_hover_color  mix(@active_element, @theme_base_color, 0.4);
+@define-color button_bottom_active_hover_color mix(@active_element, @theme_bg_color, 0.4);
+
+@define-color colored_button_top_color mix(@button_top_color, @theme_selected_bg_color, 0.2);
+@define-color colored_button_bottom_color mix(@button_bottom_color, @theme_selected_bg_color, 0.2);
+@define-color colored_button_top_hover_color mix(@button_top_hover_color, @theme_selected_bg_color, 0.2);
+@define-color colored_button_bottom_hover_color mix(@button_bottom_hover_color, @theme_selected_bg_color, 0.2);
+@define-color colored_button_top_active_color mix(@button_top_active_color, @theme_selected_bg_color, 0.2);
+@define-color colored_button_bottom_active_color mix(@button_bottom_active_color, @theme_selected_bg_color, 0.2);
+@define-color colored_button_top_active_hover_color mix(@button_top_active_hover_color, @theme_selected_bg_color, 0.2);
+@define-color colored_button_bottom_active_hover_color mix(@button_bottom_active_hover_color, @theme_selected_bg_color, 0.2);
+
+@define-color suggested_action_button_top_color mix(@theme_bg_color, shade(@theme_selected_bg_color, 1.1), 0.8);
+@define-color suggested_action_button_bottom_color mix(@theme_bg_color, shade(@theme_selected_bg_color, 0.9), 0.8);
+@define-color suggested_action_button_top_hover_color mix(@theme_bg_color, shade(@theme_selected_bg_color, 1.2), 0.8);
+@define-color suggested_action_button_bottom_hover_color mix(@theme_bg_color, shade(@theme_selected_bg_color, 1.0), 0.8);
+@define-color suggested_action_button_top_active_color mix(@theme_bg_color, shade(@theme_selected_bg_color, 0.9), 0.8);
+@define-color suggested_action_button_bottom_active_color mix(@theme_bg_color, shade(@theme_selected_bg_color, 1.0), 0.8);
+
+@define-color destructive_action_button_top_color mix(@theme_bg_color, shade(@error_color, 1.1), 0.8);
+@define-color destructive_action_button_bottom_color mix(@theme_bg_color, shade(@error_color, 0.9), 0.8);
+@define-color destructive_action_button_top_hover_color mix(@theme_bg_color, shade(@error_color, 1.2), 0.8);
+@define-color destructive_action_button_bottom_hover_color mix(@theme_bg_color, shade(@error_color, 1.0), 0.8);
+@define-color destructive_action_button_top_active_color mix(@theme_bg_color, shade(@error_color, 0.9), 0.8);
+@define-color destructive_action_button_bottom_active_color mix(@theme_bg_color, shade(@error_color, 1.0), 0.8);
+
+/*****************
+ * Fallback Mode *
+ *****************/
+@define-color os_chrome_bg_color black;
+@define-color os_chrome_fg_color #ccc;
+@define-color os_chrome_selected_bg_color #333;
+@define-color os_chrome_selected_fg_color white;
diff --git a/app-data/themes/gtk/default/gtk-dark.css b/app-data/themes/gtk/default/gtk-dark.css
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/gtk-dark.css
@@ -0,0 +1,36 @@
+@import url("reset.css");
+
+/* Dark color scheme */
+@define-color theme_base_color #2a2f25;
+@define-color active_element #caba9a;
+@define-color theme_text_color #d0d5d0;
+@define-color border_color #304020;
+@define-color internal_element_color #cacdc6;
+
+@define-color theme_selected_bg_color #658a60;
+@define-color theme_selected_fg_color #ffffff;
+
+@define-color link_color #729fcf;
+@define-color info_color #c4a000;
+@define-color warning_color #f57900;
+@define-color question_color #ad7fa8;
+@define-color error_color #ef2929;
+@define-color other_color #c17d11;
+
+
+@define-color subdued_color shade(@theme_base_color, 0.3);
+@define-color theme_bg_color #222320;
+@define-color theme_fg_color @theme_text_color;
+@define-color theme_tooltip_bg_color mix(@subdued_color, #000000, 0.5);
+@define-color theme_tooltip_fg_color @theme_text_color;
+
+@define-color placeholder_text_color @internal_element_color;
+
+@import url("gtk-common.css");
+@import url("gtk-widgets-assets-dark.css");
+@import url("gtk-widgets-backgrounds-and-texts.css");
+@import url("gtk-widgets-borders.css");
+@import url("gtk-widgets.css");
+@import url("app-specific.css");
+@import url("keybindings.css");
+
diff --git a/app-data/themes/gtk/default/gtk-fallback.css b/app-data/themes/gtk/default/gtk-fallback.css
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/gtk-fallback.css
@@ -0,0 +1,15 @@
+.menu {
+    border-image: linear-gradient(to top,
+                                  alpha(@border_color, 0.80),
+                                  alpha(@border_color, 0.60) 33%,
+                                  alpha(@border_color, 0.50) 66%,
+                                  alpha(@border_color, 0.15)) 1 1 1 1/ 1px 1px 1px 1px;
+}
+
+.menubar .menu {
+    border-image: linear-gradient(to top,
+                                  alpha(@border_color, 0.80),
+                                  alpha(@border_color, 0.60) 33%,
+                                  alpha(@border_color, 0.50) 66%,
+                                  transparent 99%) 1 1 1 1/ 1px 1px 1px 1px;
+}
diff --git a/app-data/themes/gtk/default/gtk-widgets-assets-dark.css b/app-data/themes/gtk/default/gtk-widgets-assets-dark.css
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/gtk-widgets-assets-dark.css
@@ -0,0 +1,339 @@
+/****************
+ * Content view *
+ ****************/
+.content-view.view {
+    background-image: url("assets/noise-texture-dark.png");
+}
+
+/***************
+ * Resize grip *
+ ***************/
+.grip {
+    background-image: url("assets/resize-grip-dark.svg");
+}
+
+.grip:hover {
+    background-image: url("assets/resize-grip-hover-dark.svg");
+}
+
+.grip:active {
+    background-image: url("assets/resize-grip-active.svg");
+}
+
+/*******************
+ * Pane separators *
+ *******************/
+.pane-separator {
+    background-position: center;
+    background-repeat: no-repeat;
+    background-image: url("assets/pane-separator-grip-dark.svg");
+}
+
+.pane-separator:hover {
+    background-image: url("assets/pane-separator-grip-hover-dark.svg");
+}
+
+.pane-separator:active {
+    background-image: url("assets/pane-separator-grip-active.svg");
+}
+
+.pane-separator.vertical {
+    background-image: url("assets/pane-separator-grip-vertical-dark.svg");
+}
+
+.pane-separator.vertical:hover {
+    background-image: url("assets/pane-separator-grip-vertical-hover-dark.svg");
+}
+
+.pane-separator.vertical:active {
+    background-image: url("assets/pane-separator-grip-vertical-active.svg");
+}
+
+/*************************
+ * Check and Radio items *
+ *************************/
+.check:active,
+.check row:active,
+.view.cell.check:active {
+    background-image: url("assets/check-selected.svg"),
+                      linear-gradient(to bottom,
+                                      @suggested_action_button_top_color,
+                                      @suggested_action_button_bottom_color
+                                      );
+    background-color: transparent;
+}
+
+.check:active:hover,
+.check row:active:hover,
+.view.cell.check:active:hover {
+    background-image: url("assets/check-selected.svg"),
+                      linear-gradient(to bottom,
+                                      @suggested_action_button_top_hover_color,
+                                      @suggested_action_button_bottom_hover_color
+                                      );
+    background-color: transparent;
+}
+
+.check:active:insensitive,
+.check row:active:insensitive,
+.view.cell.check:active:insensitive {
+    background-image: url("assets/check-insensitive-dark.svg");
+    background-color: @theme_bg_color;
+}
+
+.radio:active,
+.radio row:active,
+.view.cell.radio:active {
+    background-image: url("assets/radio-selected.svg"),
+                      linear-gradient(to bottom,
+                                      @suggested_action_button_top_color,
+                                      @suggested_action_button_bottom_color
+                                      );
+    background-color: transparent;
+}
+
+.radio:active:hover,
+.radio row:active:hover,
+.view.cell.radio:active:hover {
+    background-image: url("assets/radio-selected.svg"),
+                      linear-gradient(to bottom,
+                                      @suggested_action_button_top_hover_color,
+                                      @suggested_action_button_bottom_hover_color
+                                      );
+    background-color: transparent;
+}
+
+.radio:active:insensitive,
+.radio row:active:insensitive,
+.view.cell.radio:active:insensitive {
+    background-image: url("assets/radio-insensitive-dark.svg");
+    background-color: @theme_bg_color;
+}
+
+.check:inconsistent,
+.check row:inconsistent,
+.view.cell.check:inconsistent,
+.radio:inconsistent,
+.radio row:inconsistent,
+.view.cell.radio:inconsistent {
+    background-image: url("assets/mix-dark.svg"),
+                      linear-gradient(to bottom,
+                                      @colored_button_top_color,
+                                      @colored_button_bottom_color
+                                      );
+    background-color: transparent;
+}
+
+.check:inconsistent:hover,
+.check row:inconsistent:hover,
+.view.cell.check:inconsistent:hover,
+.radio:inconsistent:hover,
+.radio row:inconsistent:hover,
+.view.cell.radio:inconsistent:hover {
+    background-image: url("assets/mix-dark.svg"),
+                      linear-gradient(to bottom,
+                                      @colored_button_top_hover_color,
+                                      @colored_button_bottom_hover_color
+                                      );
+    background-color: transparent;
+}
+
+.check:inconsistent:insensitive,
+.check row:inconsistent:insensitive,
+.view.cell.check:inconsistent:insensitive,
+.radio:inconsistent:insensitive,
+.radio row:inconsistent:insensitive,
+.view.cell.radio:inconsistent:insensitive {
+    background-image: url("assets/mix-insensitive-dark.svg");
+    background-color: @theme_bg_color;
+}
+
+.menuitem.check:active {
+    background-image: url("assets/check-dark.svg");
+    background-color: transparent;
+}
+
+.menuitem.check:active:hover {
+    background-image: url("assets/check-selected.svg");
+    background-color: transparent;
+}
+
+.menuitem.check:active:insensitive {
+    background-image: url("assets/check-insensitive-dark.svg");
+    background-color: transparent;
+}
+
+.menuitem.radio:active {
+    background-image: url("assets/radio-dark.svg");
+    background-color: transparent;
+}
+
+.menuitem.radio:active:hover {
+    background-image: url("assets/radio-selected.svg");
+    background-color: transparent;
+}
+
+.menuitem.radio:active:insensitive {
+    background-image: url("assets/radio-insensitive-dark.svg");
+    background-color: transparent;
+}
+
+.menuitem.check:inconsistent,
+.menuitem.radio:inconsistent {
+    background-image: url("assets/mix-dark.svg");
+    background-color: transparent;
+}
+
+.menuitem.check:inconsistent:hover,
+.menuitem.radio:inconsistent:hover {
+    background-image: url("assets/mix-selected.svg");
+    background-color: transparent;
+}
+
+.menuitem.check:inconsistent:insensitive,
+.menuitem.radio:inconsistent:insensitive {
+    background-image: url("assets/mix-insensitive-dark.svg");
+    background-color: transparent;
+}
+
+.sidebar .view.radio:hover,
+.sidebar .view.radio:focus {
+    background-image: url("assets/radio-hover-dark.svg");
+    background-color: transparent;
+}
+
+.sidebar .view.radio:active,
+.sidebar .view.radio:active:hover,
+.sidebar .view.radio:active:focus {
+    background-image: url("assets/radio-dark.svg");
+    background-color: transparent;
+}
+
+.sidebar .view.radio:selected:hover,
+.sidebar .view.radio:selected:focus {
+    background-image: url("assets/radio-selected-hover.svg");
+    background-color: transparent; 
+}
+
+.sidebar .view.radio:selected:active,
+.sidebar .view.radio:selected:active:hover,
+.sidebar .view.radio:selected:active:focus {
+    background-image: url("assets/radio-selected.svg");
+    background-color: transparent;
+}
+
+/*********************
+ * GtkScale's slider *
+ *********************/
+.scale.scale-has-marks-above.slider.horizontal {
+    background-image: url("assets/scale-slider-up-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.horizontal:hover {
+    background-image: url("assets/scale-slider-up-hover-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.horizontal:active {
+    background-image: url("assets/scale-slider-up-active-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.fine-tune.horizontal:active {
+    background-image: url("assets/scale-slider-up-fine-tune-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.horizontal:insensitive {
+    background-image: url("assets/scale-slider-up-insensitive-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.vertical {
+    background-image: url("assets/scale-slider-left-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.vertical:hover {
+    background-image: url("assets/scale-slider-left-hover-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.vertical:active {
+    background-image: url("assets/scale-slider-left-active-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.fine-tune.vertical:active {
+    background-image: url("assets/scale-slider-left-fine-tune-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.vertical:insensitive {
+    background-image: url("assets/scale-slider-left-insensitive-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.horizontal {
+    background-image: url("assets/scale-slider-down-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.horizontal:hover {
+    background-image: url("assets/scale-slider-down-hover-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.horizontal:active {
+    background-image: url("assets/scale-slider-down-active-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.fine-tune.horizontal:active {
+    background-image: url("assets/scale-slider-down-fine-tune-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.horizontal:insensitive {
+    background-image: url("assets/scale-slider-down-insensitive-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.vertical {
+    background-image: url("assets/scale-slider-right-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.vertical:hover {
+    background-image: url("assets/scale-slider-right-hover-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.vertical:active {
+    background-image: url("assets/scale-slider-right-active-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.fine-tune.vertical:active {
+    background-image: url("assets/scale-slider-right-fine-tune-dark.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.vertical:insensitive {
+    background-image: url("assets/scale-slider-right-insensitive-dark.svg");
+    background-color: transparent;
+}
+
+/********************************
+ * Touch text selection handles *
+ ********************************/
+.cursor-handle.bottom {
+    background-image: url("assets/scale-slider-up-dark.svg");
+    background-color: transparent;
+}
+
+.cursor-handle.top {
+    background-image: url("assets/scale-slider-down-dark.svg");
+    background-color: transparent;
+}
diff --git a/app-data/themes/gtk/default/gtk-widgets-assets.css b/app-data/themes/gtk/default/gtk-widgets-assets.css
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/gtk-widgets-assets.css
@@ -0,0 +1,339 @@
+/****************
+ * Content view *
+ ****************/
+.content-view.view {
+    background-image: url("assets/noise-texture.png");
+}
+
+/***************
+ * Resize grip *
+ ***************/
+.grip {
+    background-image: url("assets/resize-grip.svg");
+}
+
+.grip:hover {
+    background-image: url("assets/resize-grip-hover.svg");
+}
+
+.grip:active {
+    background-image: url("assets/resize-grip-active.svg");
+}
+
+/*******************
+ * Pane separators *
+ *******************/
+.pane-separator {
+    background-position: center;
+    background-repeat: no-repeat;
+    background-image: url("assets/pane-separator-grip.svg");
+}
+
+.pane-separator:hover {
+    background-image: url("assets/pane-separator-grip-hover.svg");
+}
+
+.pane-separator:active {
+    background-image: url("assets/pane-separator-grip-active.svg");
+}
+
+.pane-separator.vertical {
+    background-image: url("assets/pane-separator-grip-vertical.svg");
+}
+
+.pane-separator.vertical:hover {
+    background-image: url("assets/pane-separator-grip-vertical-hover.svg");
+}
+
+.pane-separator.vertical:active {
+    background-image: url("assets/pane-separator-grip-vertical-active.svg");
+}
+
+/*************************
+ * Check and Radio items *
+ *************************/
+.check:active,
+.check row:active,
+.view.cell.check:active {
+    background-image: url("assets/check-selected.svg"),
+                      linear-gradient(to bottom,
+                                      @suggested_action_button_top_color,
+                                      @suggested_action_button_bottom_color
+                                      );
+    background-color: transparent;
+}
+
+.check:active:hover,
+.check row:active:hover,
+.view.cell.check:active:hover {
+    background-image: url("assets/check-selected.svg"),
+                      linear-gradient(to bottom,
+                                      @suggested_action_button_top_hover_color,
+                                      @suggested_action_button_bottom_hover_color
+                                      );
+    background-color: transparent;
+}
+
+.check:active:insensitive,
+.check row:active:insensitive,
+.view.cell.check:active:insensitive {
+    background-image: url("assets/check-insensitive.svg");
+    background-color: @theme_bg_color;
+}
+
+.radio:active,
+.radio row:active,
+.view.cell.radio:active {
+    background-image: url("assets/radio-selected.svg"),
+                      linear-gradient(to bottom,
+                                      @suggested_action_button_top_color,
+                                      @suggested_action_button_bottom_color
+                                      );
+    background-color: transparent;
+}
+
+.radio:active:hover,
+.radio row:active:hover,
+.view.cell.radio:active:hover {
+    background-image: url("assets/radio-selected.svg"),
+                      linear-gradient(to bottom,
+                                      @suggested_action_button_top_hover_color,
+                                      @suggested_action_button_bottom_hover_color
+                                      );
+    background-color: transparent;
+}
+
+.radio:active:insensitive,
+.radio row:active:insensitive,
+.view.cell.radio:active:insensitive {
+    background-image: url("assets/radio-insensitive.svg");
+    background-color: @theme_bg_color;
+}
+
+.check:inconsistent,
+.check row:inconsistent,
+.view.cell.check:inconsistent,
+.radio:inconsistent,
+.radio row:inconsistent,
+.view.cell.radio:inconsistent {
+    background-image: url("assets/mix.svg"),
+                      linear-gradient(to bottom,
+                                      @colored_button_top_color,
+                                      @colored_button_bottom_color
+                                      );
+    background-color: transparent;
+}
+
+.check:inconsistent:hover,
+.check row:inconsistent:hover,
+.view.cell.check:inconsistent:hover,
+.radio:inconsistent:hover,
+.radio row:inconsistent:hover,
+.view.cell.radio:inconsistent:hover {
+    background-image: url("assets/mix.svg"),
+                      linear-gradient(to bottom,
+                                      @colored_button_top_hover_color,
+                                      @colored_button_bottom_hover_color
+                                      );
+    background-color: transparent;
+}
+
+.check:inconsistent:insensitive,
+.check row:inconsistent:insensitive,
+.view.cell.check:inconsistent:insensitive,
+.radio:inconsistent:insensitive,
+.radio row:inconsistent:insensitive,
+.view.cell.radio:inconsistent:insensitive {
+    background-image: url("assets/mix-insensitive.svg");
+    background-color: @theme_bg_color;
+}
+
+.menuitem.check:active {
+    background-image: url("assets/check.svg");
+    background-color: transparent;
+}
+
+.menuitem.check:active:hover {
+    background-image: url("assets/check-selected.svg");
+    background-color: transparent;
+}
+
+.menuitem.check:active:insensitive {
+    background-image: url("assets/check-insensitive.svg");
+    background-color: transparent;
+}
+
+.menuitem.radio:active {
+    background-image: url("assets/radio.svg");
+    background-color: transparent;
+}
+
+.menuitem.radio:active:hover {
+    background-image: url("assets/radio-selected.svg");
+    background-color: transparent;
+}
+
+.menuitem.radio:active:insensitive {
+    background-image: url("assets/radio-insensitive.svg");
+    background-color: transparent;
+}
+
+.menuitem.check:inconsistent,
+.menuitem.radio:inconsistent {
+    background-image: url("assets/mix.svg");
+    background-color: transparent;
+}
+
+.menuitem.check:inconsistent:hover,
+.menuitem.radio:inconsistent:hover {
+    background-image: url("assets/mix-selected.svg");
+    background-color: transparent;
+}
+
+.menuitem.check:inconsistent:insensitive,
+.menuitem.radio:inconsistent:insensitive {
+    background-image: url("assets/mix-insensitive.svg");
+    background-color: transparent;
+}
+
+.sidebar .view.radio:hover,
+.sidebar .view.radio:focus {
+    background-image: url("assets/radio-hover.svg");
+    background-color: transparent;
+}
+
+.sidebar .view.radio:active,
+.sidebar .view.radio:active:hover,
+.sidebar .view.radio:active:focus {
+    background-image: url("assets/radio.svg");
+    background-color: transparent;
+}
+
+.sidebar .view.radio:selected:hover,
+.sidebar .view.radio:selected:focus {
+    background-image: url("assets/radio-selected-hover.svg");
+    background-color: transparent; 
+}
+
+.sidebar .view.radio:selected:active,
+.sidebar .view.radio:selected:active:hover,
+.sidebar .view.radio:selected:active:focus {
+    background-image: url("assets/radio-selected.svg");
+    background-color: transparent;
+}
+
+/*********************
+ * GtkScale's slider *
+ *********************/
+.scale.scale-has-marks-above.slider.horizontal {
+    background-image: url("assets/scale-slider-up.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.horizontal:hover {
+    background-image: url("assets/scale-slider-up-hover.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.horizontal:active {
+    background-image: url("assets/scale-slider-up-active.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.fine-tune.horizontal:active {
+    background-image: url("assets/scale-slider-up-fine-tune.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.horizontal:insensitive {
+    background-image: url("assets/scale-slider-up-insensitive.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.vertical {
+    background-image: url("assets/scale-slider-left.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.vertical:hover {
+    background-image: url("assets/scale-slider-left-hover.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.vertical:active {
+    background-image: url("assets/scale-slider-left-active.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.fine-tune.vertical:active {
+    background-image: url("assets/scale-slider-left-fine-tune.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-above.slider.vertical:insensitive {
+    background-image: url("assets/scale-slider-left-insensitive.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.horizontal {
+    background-image: url("assets/scale-slider-down.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.horizontal:hover {
+    background-image: url("assets/scale-slider-down-hover.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.horizontal:active {
+    background-image: url("assets/scale-slider-down-active.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.fine-tune.horizontal:active {
+    background-image: url("assets/scale-slider-down-fine-tune.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.horizontal:insensitive {
+    background-image: url("assets/scale-slider-down-insensitive.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.vertical {
+    background-image: url("assets/scale-slider-right.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.vertical:hover {
+    background-image: url("assets/scale-slider-right-hover.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.vertical:active {
+    background-image: url("assets/scale-slider-right-active.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.fine-tune.vertical:active {
+    background-image: url("assets/scale-slider-right-fine-tune.svg");
+    background-color: transparent;
+}
+
+.scale.scale-has-marks-below.slider.vertical:insensitive {
+    background-image: url("assets/scale-slider-right-insensitive.svg");
+    background-color: transparent;
+}
+
+/********************************
+ * Touch text selection handles *
+ ********************************/
+.cursor-handle.bottom {
+    background-image: url("assets/scale-slider-up.svg");
+    background-color: transparent;
+}
+
+.cursor-handle.top {
+    background-image: url("assets/scale-slider-down.svg");
+    background-color: transparent;
+}
diff --git a/app-data/themes/gtk/default/gtk-widgets-backgrounds-and-texts.css b/app-data/themes/gtk/default/gtk-widgets-backgrounds-and-texts.css
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/gtk-widgets-backgrounds-and-texts.css
@@ -0,0 +1,486 @@
+/* Button and slider background and text */
+.button,
+.toolbar .button.raised,
+.toolbar .raised .button,
+.primary-toolbar .button:focus,
+.primary-toolbar .spinbutton .button,
+.primary-toolbar GtkComboBox .button,
+.primary-toolbar GtkComboBox.combobox-entry .button,
+column-header.button,
+column-header .button,
+.scale.slider,
+GtkSwitch.slider,
+.scrollbar.slider,
+.check,
+.check row,
+.view.cell.check,
+.radio,
+.radio row,
+.view.cell.radio,
+.notebook tab {
+    background-image: linear-gradient(to bottom,
+                                      @button_top_color,
+                                      @button_bottom_color
+                                      );
+    background-color: transparent;
+    color: @theme_fg_color;
+}
+
+.scrollbar.slider.vertical {
+    background-image: linear-gradient(to bottom,
+                                      @button_top_color,
+                                      alpha(@button_top_color, 0) 8px
+                                      ),
+                      linear-gradient(to top,
+                                      @button_bottom_color,
+                                      alpha(@button_bottom_color, 0) 8px
+                                      );
+    background-color: mix(@button_top_color, @button_bottom_color, 0.5);
+}
+
+/* Prelit button and slider background and text */
+.button:hover,
+.toolbar .button.raised:hover,
+.toolbar .raised .button:hover,
+.primary-toolbar .button:hover,
+.primary-toolbar .spinbutton .button:hover,
+.primary-toolbar GtkComboBox .button:hover,
+.primary-toolbar GtkComboBox.combobox-entry .button:hover,
+column-header.button:hover,
+column-header .button:hover,
+.scale.slider:hover,
+GtkSwitch.slider:hover,
+.scrollbar.slider:hover,
+.check:hover,
+.check row:hover,
+.view.cell.check:hover,
+.radio:hover,
+.radio row:hover,
+.view.cell.radio:hover,
+.notebook tab:hover {
+    background-image: linear-gradient(to bottom,
+                                      @button_top_hover_color,
+                                      @button_bottom_hover_color
+                                      );
+    background-color: transparent;
+    color: @theme_fg_color;
+}
+
+.scrollbar.slider.vertical:hover {
+    background-image: linear-gradient(to bottom,
+                                      @button_top_hover_color,
+                                      alpha(@button_top_hover_color, 0) 8px
+                                      ),
+                      linear-gradient(to top,
+                                      @button_bottom_hover_color,
+                                      alpha(@button_bottom_hover_color, 0) 8px
+                                      );
+    background-color: mix(@button_top_hover_color, @button_bottom_hover_color, 0.5);
+}
+
+/* Pushed button and through background and text */
+.button:active,
+.toolbar .button.raised:active,
+.toolbar .raised .button:active,
+.primary-toolbar .button:active,
+.primary-toolbar .spinbutton .button:active,
+.primary-toolbar GtkComboBox .button:active,
+.primary-toolbar GtkComboBox.combobox-entry .button:active,
+column-header.button:active,
+column-header .button:active,
+column-header .button.dnd,
+.trough,
+.trough row,
+.scale.trough,
+GtkSwitch.trough,
+.scrollbar.trough {
+    background-image: linear-gradient(to bottom,
+                                      @button_top_active_color,
+                                      @button_bottom_active_color
+                                      );
+    background-color: transparent;
+    color: @theme_fg_color;
+}
+
+.scrollbar.trough.vertical {
+    background-image: linear-gradient(to bottom,
+                                      @button_top_active_color,
+                                      alpha(@button_top_active_color, 0) 8px
+                                      ),
+                      linear-gradient(to top,
+                                      @button_bottom_active_color,
+                                      alpha(@button_bottom_active_color, 0) 8px
+                                      );
+    background-color: mix(@button_top_active_color, @button_bottom_active_color, 0.5);
+}
+
+.trough.vertical {
+    background-image: linear-gradient(to bottom,
+                                      @button_top_active_color,
+                                      alpha(@button_top_active_color, 0) 9px
+                                      ),
+                      linear-gradient(to top,
+                                      @button_bottom_active_color,
+                                      alpha(@button_bottom_active_color, 0) 9px
+                                      );
+    background-color: mix(@button_top_active_color, @button_bottom_active_color, 0.5);
+}
+
+.scale.trough.vertical {
+    background-image: linear-gradient(to bottom,
+                                      @button_top_active_color,
+                                      alpha(@button_top_active_color, 0) 3px
+                                      ),
+                      linear-gradient(to top,
+                                      @button_bottom_active_color,
+                                      alpha(@button_bottom_active_color, 0) 3px
+                                      );
+    background-color: mix(@button_top_active_color, @button_bottom_active_color, 0.5);
+}
+
+/* Prelit pushed button background and text */
+.button:active:hover,
+.toolbar .button.raised:active:hover,
+.toolbar .raised .button:active:hover,
+.primary-toolbar .button:active:hover,
+.primary-toolbar .spinbutton .button:active:hover,
+.primary-toolbar GtkComboBox .button:active:hover,
+.primary-toolbar GtkComboBox.combobox-entry .button:active:hover,
+column-header.button:active:hover,
+column-header .button:active:hover {
+    background-image: linear-gradient(to bottom,
+                                      @button_top_active_hover_color,
+                                      @button_bottom_active_hover_color
+                                      );
+    background-color: transparent;
+    color: @theme_fg_color;
+}
+
+/* Colored button background and text */
+.button.default,
+.toolbar .button.raised.default,
+.toolbar .raised .button.default,
+.primary-toolbar .button.default:focus {
+    background-image: linear-gradient(to bottom,
+                                      @colored_button_top_color,
+                                      @colored_button_bottom_color
+                                      );
+    background-color: transparent;
+    color: @theme_fg_color;
+}
+
+/* Prelit colored button background and text */
+.button.default:hover,
+.toolbar .button.raised.default:hover,
+.toolbar .raised .button.default:hover,
+.primary-toolbar .button.default:hover,
+.scale.slider.fine-tune:active,
+.scrollbar.slider.fine-tune:active {
+    background-image: linear-gradient(to bottom, 
+                                      @colored_button_top_hover_color, 
+                                      @colored_button_bottom_hover_color
+                                      );
+    background-color: transparent;
+    color: @theme_fg_color;
+}
+
+.scrollbar.slider.fine-tune.vertical:active {
+    background-image: linear-gradient(to bottom,
+                                      @colored_button_top_hover_color,
+                                      alpha(@colored_button_top_hover_color, 0) 8px
+                                      ),
+                      linear-gradient(to top,
+                                      @colored_button_bottom_hover_color,
+                                      alpha(@colored_button_bottom_hover_color, 0) 8px
+                                      );
+    background-color: mix(@colored_button_top_hover_color, @colored_button_bottom_hover_color, 0.5);
+}
+
+/* Pushed colored button background and text */
+.button.default:active,
+.toolbar .button.raised.default:active,
+.toolbar .raised .button.default:active,
+.primary-toolbar .button.default:active {
+    background-image: linear-gradient(to bottom, 
+                                      @colored_button_top_active_color, 
+                                      @colored_button_bottom_active_color
+                                      );
+    background-color: transparent;
+    color: @theme_fg_color;
+}
+
+/* Prelit pushed colored button background and text */
+.button.default:active:hover,
+.toolbar .button.raised.default:active:hover,
+.toolbar .raised .button.default:active:hover,
+.primary-toolbar .button.default:active:hover {
+    background-image: linear-gradient(to bottom, 
+                                      @colored_button_top_active_hover_color, 
+                                      @colored_button_bottom_active_hover_color
+                                      );
+    background-color: transparent;
+    color: @theme_fg_color;
+}
+
+/* Suggested action button and progressbar background and text */
+.suggested-action.button,
+.primary-toolbar .suggested-action.button,
+.selection-mode.toolbar GtkToolButton.suggested-action .button,
+.progressbar,
+.progressbar row,
+.scale.progressbar {
+    background-image: linear-gradient(to bottom,
+                                      @suggested_action_button_top_color,
+                                      @suggested_action_button_bottom_color
+                                      );
+    background-color: transparent;
+    color: @theme_selected_fg_color;
+}
+
+.progressbar.vertical {
+    background-image: linear-gradient(to bottom,
+                                      @suggested_action_button_top_color,
+                                      alpha(@suggested_action_button_top_color, 0) 9px
+                                      ),
+                      linear-gradient(to top,
+                                      @suggested_action_button_bottom_color,
+                                      alpha(@suggested_action_button_bottom_color, 0) 9px
+                                      );
+    background-color: mix(@suggested_action_button_top_color, @suggested_action_button_bottom_color, 0.5);
+}
+
+.scale.progressbar.vertical {
+    background-image: linear-gradient(to bottom,
+                                      @suggested_action_button_top_color,
+                                      alpha(@suggested_action_button_top_color, 0) 3px
+                                      ),
+                      linear-gradient(to top,
+                                      @suggested_action_button_bottom_color,
+                                      alpha(@suggested_action_button_bottom_color, 0) 3px
+                                      );
+    background-color: mix(@suggested_action_button_top_color, @suggested_action_button_bottom_color, 0.5);
+}
+
+/* Prelit suggested action button background and text */
+.suggested-action.button:hover,
+.primary-toolbar .suggested-action.button:hover,
+.selection-mode.toolbar GtkToolButton.suggested-action .button:hover {
+    background-image: linear-gradient(to bottom, 
+                                      @suggested_action_button_top_hover_color, 
+                                      @suggested_action_button_bottom_hover_color
+                                      );
+    background-color: transparent;
+    color: @theme_selected_fg_color;
+}
+
+/* Pushed suggested action button and highlit through background and text */
+.suggested-action.button:active,
+.primary-toolbar .suggested-action.button:active,
+.selection-mode.toolbar GtkToolButton.suggested-action .button:active,
+.scale.trough.highlight,
+GtkSwitch.trough:active {
+    background-image: linear-gradient(to bottom, 
+                                      @suggested_action_button_top_active_color, 
+                                      @suggested_action_button_bottom_active_color
+                                      );
+    background-color: transparent;
+    color: @theme_selected_fg_color;
+}
+
+.scale.trough.highlight.vertical {
+    background-image: linear-gradient(to bottom,
+                                      @suggested_action_button_top_active_color,
+                                      alpha(@suggested_action_button_top_active_color, 0) 3px
+                                      ),
+                      linear-gradient(to top,
+                                      @suggested_action_button_bottom_active_color,
+                                      alpha(@suggested_action_button_bottom_active_color, 0) 3px
+                                      );
+    background-color: mix(@suggested_action_button_top_active_color, @suggested_action_button_bottom_active_color, 0.5);
+}
+
+/* Destructive action button background and text */
+.destructive-action.button,
+.primary-toolbar .destructive-action.button {
+    background-image: linear-gradient(to bottom,
+                                      @destructive_action_button_top_color,
+                                      @destructive_action_button_bottom_color
+                                      );
+    background-color: transparent;
+    color: @theme_selected_fg_color;
+}
+
+/* Prelit destructive action button background and text */
+.destructive-action.button:hover,
+.primary-toolbar .destructive-action.button:hover {
+    background-image: linear-gradient(to bottom, 
+                                      @destructive_action_button_top_hover_color, 
+                                      @destructive_action_button_bottom_hover_color
+                                      );
+    background-color: transparent;
+    color: @theme_selected_fg_color;
+}
+
+/* Pushed destructive action button background and text */
+.destructive-action.button:active,
+.primary-toolbar .destructive-action.button:active {
+    background-image: linear-gradient(to bottom, 
+                                      @destructive_action_button_top_active_color, 
+                                      @destructive_action_button_bottom_active_color
+                                      );
+    background-color: transparent;
+    color: @theme_selected_fg_color;
+}
+
+/* Insensitive button and slider background and text */
+.button:insensitive,
+.toolbar .button.raised:insensitive,
+.toolbar .raised .button:insensitive,
+.primary-toolbar .spinbutton .button:insensitive,
+.primary-toolbar GtkComboBox .button:insensitive,
+.primary-toolbar GtkComboBox.combobox-entry .button:insensitive,
+column-header.button:insensitive,
+column-header .button:insensitive,
+.scale.slider:insensitive,
+GtkSwitch.slider:insensitive,
+.scrollbar.slider:insensitive,
+.check:insensitive,
+.check row:insensitive,
+.view.cell.check:insensitive,
+.radio:insensitive,
+.radio row:insensitive,
+.view.cell.radio:insensitive,
+.notebook tab:insensitive,
+.button.default:insensitive,
+.toolbar .button.raised.default:insensitive,
+.toolbar .raised .button.default:insensitive,
+.suggested-action.button:insensitive,
+.primary-toolbar .suggested-action.button:insensitive,
+.selection-mode.toolbar GtkToolButton.suggested-action .button:insensitive,
+.progressbar:insensitive,
+.progressbar row:insensitive,
+.scale.progressbar:insensitive,
+.destructive-action.button:insensitive,
+.primary-toolbar .destructive-action.button:insensitive {
+    background-image: none;
+    background-color: mix(@theme_bg_color, mix(@button_top_color, @button_bottom_color, 0.5), 0.5);
+    color: @insensitive_fg_color;
+}
+
+/* Insensitive Pushed button and through background and text */
+.button:active:insensitive,
+.toolbar .button.raised:active:insensitive,
+.toolbar .raised .button:active:insensitive,
+.primary-toolbar .button:active:insensitive,
+.primary-toolbar .spinbutton .button:active:insensitive,
+.primary-toolbar GtkComboBox .button:active:insensitive,
+.primary-toolbar GtkComboBox.combobox-entry .button:active:insensitive,
+column-header.button:active:insensitive,
+column-header .button:active:insensitive,
+.trough:insensitive,
+.trough row:insensitive,
+.scale.trough:insensitive,
+GtkSwitch.trough:insensitive,
+.scrollbar.trough:insensitive,
+.button.default:active:insensitive,
+.toolbar .button.raised.default:active:insensitive,
+.toolbar .raised .button.default:active:insensitive,
+.primary-toolbar .button.default:active:insensitive,
+.suggested-action.button:active:insensitive,
+.primary-toolbar .suggested-action.button:active:insensitive,
+.selection-mode.toolbar GtkToolButton.suggested-action .button:active:insensitive,
+.scale.trough.highlight:insensitive,
+GtkSwitch.trough:active:insensitive,
+.destructive-action.button:active:insensitive,
+.primary-toolbar .destructive-action.button:active:insensitive {
+    background-image: none;
+    background-color: mix(@theme_bg_color, mix(@button_top_active_color, @button_bottom_active_color, 0.5), 0.5);
+    color: @insensitive_fg_color;
+}
+
+/* Active menu button and tab background and text */
+.menubar .menuitem:hover,
+.button.menuitem.menubar:active,
+GtkMenuButton.button:active,
+.toolbar GtkMenuButton.button.raised:active,
+.toolbar .raised GtkMenuButton.button:active,
+.primary-toolbar GtkMenuButton.button:active,
+.selection-mode.toolbar .selection-menu.button:active,
+.selection-mode.header-bar .selection-menu.button:active,
+GtkComboBox .button:active,
+GtkComboBox.combobox-entry .button:active,
+.notebook tab:active {
+    background-image: none;
+    background-color: @theme_base_color;
+    color: @theme_text_color;
+}
+
+/* Entry background and text */
+.entry {
+    background-image: linear-gradient(to bottom,
+                                      alpha(@theme_tooltip_bg_color, 0.2),
+                                      alpha(@theme_tooltip_bg_color, 0) 4px
+                                      );
+    background-color: @theme_base_color;
+    color: @theme_text_color;
+}
+
+/* Insensitive entry background and text */
+.entry:insensitive {
+    background-image: none;
+    background-color: @alt_base_color;
+    color: @insensitive_fg_color;
+}
+
+/* Toolbar and header bar background */
+.toolbar,
+.header-bar {
+    background-image: none;
+    background-color: @theme_bg_color;
+}
+
+/* Unfocused window toolbar and header bar background */
+.toolbar:backdrop,
+.header-bar:backdrop {
+    background-image: none;
+    background-color: @theme_bg_color;
+}
+
+/* Selection mode toolbar and header bar background */
+.selection-mode.header-bar {
+    background-image: linear-gradient(to bottom,
+                                      mix(@info_color, @button_top_color, 0.8),
+                                      mix(@info_color, @theme_bg_color, 0.8)
+                                      );
+    background-color: transparent;
+}
+
+/* Unfocused window selection mode toolbar and header bar background */
+.selection-mode.toolbar,
+.selection-mode.toolbar:backdrop,
+.selection-mode.header-bar:backdrop {
+    background-image: none;
+    background-color: mix(@info_color, @theme_bg_color, 0.8);
+}
+
+/* Primary and inline toolbars background */
+.primary-toolbar .toolbar,
+.primary-toolbar.toolbar,
+.inline-toolbar.toolbar {
+    background-image: linear-gradient(to bottom,
+                                      mix(@subdued_color, @theme_tooltip_bg_color, 0.2),
+                                      mix(@subdued_color, @theme_bg_color, 0.2)
+                                      );
+    background-color: transparent;
+}
+
+/* Unfocused window primary toolbar and insensitive inline toolbar background */
+.primary-toolbar .toolbar:backdrop,
+.primary-toolbar.toolbar:backdrop,
+.inline-toolbar.toolbar:insensitive,
+.primary-toolbar .toolbar.vertical,
+.primary-toolbar.toolbar.vertical,
+.inline-toolbar.toolbar.vertical {
+    background-image: none;
+    background-color: @subdued_color;
+}
diff --git a/app-data/themes/gtk/default/gtk-widgets-borders.css b/app-data/themes/gtk/default/gtk-widgets-borders.css
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/gtk-widgets-borders.css
@@ -0,0 +1,215 @@
+/* Generic element borders */
+.entry,
+.button,
+.toolbar .button.raised,
+.toolbar .raised .button,
+.primary-toolbar .button:hover,
+.primary-toolbar .button:active,
+.primary-toolbar .spinbutton .button,
+.primary-toolbar GtkComboBox .button,
+.primary-toolbar GtkComboBox.combobox-entry .button,
+column-header.button,
+column-header .button,
+.scale.slider,
+GtkSwitch.slider,
+.trough,
+.trough row,
+.scale.trough,
+GtkSwitch.trough,
+.notebook tab {
+    border-radius: 4px;
+    border-width: 1px;
+    border-style: solid;
+    border-color: @alt_border_color;
+}
+
+.scrollbar.slider,
+.scrollbar.trough,
+.check,
+.check row,
+.view.cell.check,
+.radio,
+.radio row,
+.view.cell.radio {
+    box-shadow: inset 0 0 0 1px @alt_border_color;
+}
+
+/* Colored element borders */
+.button.default,
+.toolbar .button.raised.default,
+.toolbar .raised .button.default,
+.primary-toolbar .button.default:hover,
+.primary-toolbar .button.default:active {
+    border-radius: 4px;
+    border-width: 1px;
+    border-style: solid;
+    border-color: @colored_border_color;
+}
+
+.check:inconsistent,
+.check row:inconsistent,
+.view.cell.check:inconsistent,
+.radio:inconsistent,
+.radio row:inconsistent,
+.view.cell.radio:inconsistent {
+    box-shadow: inset 0 0 0 1px @colored_border_color;
+}
+
+/* Suggested action buttons borders */
+.suggested-action.button,
+.primary-toolbar .suggested-action.button,
+.selection-mode.toolbar GtkToolButton.suggested-action .button,
+.progressbar,
+.progressbar row,
+.scale.progressbar,
+.scale.trough.highlight,
+GtkSwitch.trough:active {
+    border-radius: 4px;
+    border-width: 1px;
+    border-style: solid;
+    border-color: @suggested_action_border_color;
+}
+
+.check:active,
+.check row:active,
+.view.cell.check:active,
+.radio:active,
+.radio row:active,
+.view.cell.radio:active {
+    box-shadow: inset 0 0 0 1px @suggested_action_border_color;
+}
+
+/* Destructive action buttons borders */
+.destructive-action.button,
+.primary-toolbar .destructive-action.button {
+    border-radius: 4px;
+    border-width: 1px;
+    border-style: solid;
+    border-color: @destructive_action_border_color;
+}
+
+/* Focused element borders */
+.entry:focus,
+.button:focus,
+.toolbar .button.raised:focus,
+.toolbar .raised .button:focus,
+.primary-toolbar .button:focus,
+.primary-toolbar .spinbutton .button:focus,
+.primary-toolbar GtkComboBox .button:focus,
+.primary-toolbar GtkComboBox.combobox-entry .button:focus,
+column-header.button:focus,
+column-header .button:focus,
+.scale.slider:active,
+/* GtkSwitch.slider:active, */
+.scale.trough:focus,
+GtkSwitch.trough:focus,
+.notebook tab:active:focus {
+    border-color: @focused_border_color;
+    box-shadow: inset 0 0 0 1px alpha(@focused_border_color, 0.2);
+}
+
+.scrollbar.slider:active,
+.check:focus,
+.check row:focus,
+.view.cell.check:focus,
+.radio:focus,
+.radio row:focus,
+.view.cell.radio:focus {
+    box-shadow: inset 0 0 0 2px alpha(@focused_border_color, 0.2),
+                inset 0 0 0 1px @focused_border_color;
+}
+
+/* Focused colored element borders */
+.button.default:focus,
+.toolbar .button.raised.default:focus,
+.toolbar .raised .button.default:focus,
+.primary-toolbar .button.default:focus,
+.scale.slider.fine-tune:active {
+    border-color: @focused_border_color;
+    box-shadow: inset 0 0 0 1px alpha(@focused_border_color, 0.2);
+}
+
+.scrollbar.slider.fine-tune:active,
+.check:inconsistent:focus,
+.check row:inconsistent:focus,
+.view.cell.check:inconsistent:focus,
+.radio:inconsistent:focus,
+.radio row:inconsistent:focus,
+.view.cell.radio:inconsistent:focus {
+    box-shadow: inset 0 0 0 2px alpha(@focused_border_color, 0.2),
+                inset 0 0 0 1px @focused_border_color;
+}
+
+/* Focused suggested action buttons borders */
+.suggested-action.button:focus,
+.primary-toolbar .suggested-action.button:focus,
+.selection-mode.toolbar GtkToolButton.suggested-action .button:focus,
+.scale.progressbar:focus,
+.scale.trough.highlight:focus,
+GtkSwitch.trough:active:focus {
+    border-color: @suggested_action_border_color;
+    box-shadow: inset 0 0 0 1px alpha(@theme_selected_fg_color, 0.6);
+}
+
+.check:active:focus,
+.check row:active:focus,
+.view.cell.check:active:focus,
+.radio:active:focus,
+.radio row:active:focus,
+.view.cell.radio:active:focus {
+    box-shadow: inset 0 0 0 2px alpha(@theme_selected_fg_color, 0.6),
+                inset 0 0 0 1px @suggested_action_border_color;
+}
+
+/* Focused destructive action buttons borders */
+.destructive-action.button:focus,
+.primary-toolbar .destructive-action.button:focus {
+    border-color: @destructive_action_border_color;
+    box-shadow: inset 0 0 0 1px alpha(@theme_selected_fg_color, 0.6);
+}
+
+/* Insensitive element borders */
+.entry:insensitive,
+.button:insensitive,
+.toolbar .button.raised:insensitive,
+.toolbar .raised .button:insensitive,
+.primary-toolbar .button:active:insensitive,
+.primary-toolbar .spinbutton .button:insensitive,
+.primary-toolbar GtkComboBox .button:insensitive,
+.primary-toolbar GtkComboBox.combobox-entry .button:insensitive,
+column-header.button:insensitive,
+column-header .button:insensitive,
+.scale.slider:insensitive,
+GtkSwitch.slider:insensitive,
+.trough:insensitive,
+.trough row:insensitive,
+.scale.trough:insensitive,
+GtkSwitch.trough:insensitive,
+.notebook tab:insensitive,
+.button.default:insensitive,
+.toolbar .button.raised.default:insensitive,
+.toolbar .raised .button.default:insensitive,
+.primary-toolbar .button.default:active:insensitive,
+.suggested-action.button:insensitive,
+.primary-toolbar .suggested-action.button:insensitive,
+.selection-mode.toolbar GtkToolButton.suggested-action .button:insensitive,
+.progressbar:insensitive,
+.progressbar row:insensitive,
+.scale.progressbar:insensitive,
+.scale.trough.highlight:insensitive,
+GtkSwitch.trough:active:insensitive,
+.destructive-action.button:insensitive,
+.primary-toolbar .destructive-action.button:insensitive {
+    border-color: @insensitive_border_color;
+}
+
+.scrollbar.slider:insensitive,
+.scrollbar.trough:insensitive,
+.check:insensitive,
+.check row:insensitive,
+.view.cell.check:insensitive,
+.radio:insensitive,
+.radio row:insensitive,
+.view.cell.radio:insensitive {
+    box-shadow: inset 0 0 0 1px @insensitive_border_color;
+}
diff --git a/app-data/themes/gtk/default/gtk-widgets.css b/app-data/themes/gtk/default/gtk-widgets.css
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/gtk-widgets.css
@@ -0,0 +1,2178 @@
+* {
+    padding: 0;
+    background-clip: border-box;
+
+    -GtkWidget-link-color: @link_color;
+    -GtkWidget-visited-link-color: @link_color;
+    -GtkIMHtml-hyperlink-color: @link_color;
+    -GtkHTML-link-color: @link_color;
+
+    -GtkTextView-error-underline-color: @error_color;
+
+    -GtkPaned-handle-size: 4;
+
+    -GtkWindow-resize-grip-width: 0;
+    -GtkWindow-resize-grip-height: 0;
+
+    -GtkCheckButton-indicator-size: 16;
+    -GtkCheckMenuItem-indicator-size: 16;
+
+    -GtkToolItemGroup-expander-size: 12;
+    -GtkExpander-expander-size: 12;
+    -GtkTreeView-expander-size: 12;
+
+    /* The size for scrollbars. The slider is 2px smaller, but we keep it
+     * up so that the whole area is sensitive to button presses for the
+     * slider. The stepper button is larger in both directions, the slider
+     * only in the width
+     */
+    -GtkScrolledWindow-scrollbar-spacing: 0;
+    -GtkScrolledWindow-scrollbars-within-bevel: 1;
+
+    -GtkToolButton-icon-spacing: 4;
+
+    -GtkTreeView-horizontal-separator: 4;
+
+    -GtkMenu-horizontal-padding: 0;
+    -GtkMenu-vertical-padding: 0;
+
+    -WnckTasklist-fade-overlay-rect: 0;
+
+    -GtkWidget-focus-padding: 0;
+    -GtkWidget-focus-line-width: 0;
+
+    -GtkWidget-cursor-color: white;
+
+    outline-offset: -3px;
+    /* outline-radius: 4px; unsupported by older gtk */
+    outline-style: dashed;
+    outline-color: @internal_element_color;
+}
+
+/***************
+ * Base States *
+ ***************/
+.background {
+    background-color: @theme_bg_color;
+    color: @theme_fg_color;
+}
+
+/* FIXME: why do we still need this? */
+GtkClutterOffscreen {
+    background-color: @theme_bg_color;
+    color: @theme_fg_color;
+}
+
+*:insensitive {
+    border-color: @insensitive_border_color;
+    color: @insensitive_fg_color;
+}
+
+*:selected {
+    background-color: @theme_selected_bg_color;
+    color: @theme_selected_fg_color;
+}
+
+*:insensitive:selected {
+    color: mix(@theme_selected_fg_color, @theme_selected_bg_color, 0.5);
+}
+
+.gtkstyle-fallback {
+    background-color: @theme_bg_color;
+    color: @theme_fg_color;
+}
+
+.gtkstyle-fallback:hover {
+    background-color: shade(@theme_bg_color, 1.1);
+    color: @theme_fg_color;
+}
+
+.gtkstyle-fallback:active {
+    background-color: shade(@theme_bg_color, 0.9);
+    color: @theme_fg_color;
+}
+
+.gtkstyle-fallback:insensitive {
+    background-color: @theme_bg_color;
+    color: @insensitive_fg_color;
+}
+
+.gtkstyle-fallback:selected {
+    background-color: @theme_selected_bg_color;
+    color: @theme_selected_fg_color;
+}
+
+GtkImage,
+GtkImage:insensitive,
+GtkLabel,
+GtkLabel:insensitive {
+    background-color: transparent;
+}
+
+/*****************
+ * Miscellaneous *
+ *****************/
+@keyframes spinner {
+    0.00% { background-image: -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.875)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.750)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.625)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.500)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.375)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.250)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.125)), to(transparent)); }
+
+    12.5% { background-image: -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.875)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.750)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.625)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.500)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.375)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.250)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.125)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)); }
+
+    25.0% { background-image: -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.750)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.625)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.500)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.375)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.250)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.125)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.875)), to(transparent)); }
+
+    37.5% { background-image: -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.625)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.500)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.375)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.250)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.125)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.875)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.750)), to(transparent)); }
+
+    50.0% { background-image: -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.500)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.375)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.250)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.125)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.875)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.750)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.625)), to(transparent)); }
+
+    62.5% { background-image: -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.375)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.250)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.125)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.875)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.750)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.625)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.500)), to(transparent)); }
+
+    75.0% { background-image: -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.250)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.125)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.875)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.750)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.625)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.500)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.375)), to(transparent)); }
+
+    87.5% { background-image: -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.125)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.875)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.750)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.625)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.500)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.375)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.250)), to(transparent)); }
+
+    100%  { background-image: -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.875)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.750)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.625)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.500)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.375)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.250)), to(transparent)),
+                              -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.125)), to(transparent)); }
+}
+
+.spinner {
+    background-color: transparent;
+    background-image: -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)),
+                      -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)),
+                      -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)),
+                      -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)),
+                      -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)),
+                      -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)),
+                      -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent)),
+                      -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent));
+    background-position: 14% 14%, 0% 50%, 14% 86%, 50% 100%, 86% 86%, 100% 50%, 86% 14%, 50% 0%;
+    background-size: 20% 20%;
+    background-repeat: no-repeat;
+}
+
+.spinner:active {
+    background-image: -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.875)), to(transparent)),
+                      -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.750)), to(transparent)),
+                      -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.625)), to(transparent)),
+                      -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.500)), to(transparent)),
+                      -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.375)), to(transparent)),
+                      -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.250)), to(transparent)),
+                      -gtk-gradient(radial, center center, 0, center center, 0.5, to(alpha(currentColor, 0.125)), to(transparent)),
+                      -gtk-gradient(radial, center center, 0, center center, 0.5, to(currentColor), to(transparent));
+    animation: spinner 1s infinite linear;
+}
+
+.button .spinner:active {
+    color: @theme_fg_color;
+}
+
+GtkStatusbar {
+    -GtkStatusbar-shadow-type: none;
+
+    padding: 3px;
+    color: @theme_fg_color;
+}
+
+.rubberband,
+.view.rubberband,
+.content-view.view.rubberband {
+    border-width: 1px;
+    border-style: solid;
+    border-color: alpha (@theme_selected_bg_color, 0.8);
+    background-color: alpha (@theme_selected_bg_color, 0.2);
+}
+
+.dnd,
+GtkTreeView.dnd {
+    border-width: 2px;
+    border-style: solid;
+    border-color: @internal_element_color;
+}
+
+.separator,
+GtkTreeView.separator,
+GtkTreeView.view.separator {
+    background-color: transparent;
+    color: @insensitive_border_color;
+}
+
+.pane-separator {
+    background-color: transparent;
+    color: @internal_element_color;
+}
+
+.pane-separator:hover {
+    color: @theme_fg_color;
+}
+
+.pane-separator:active {
+    color: @theme_selected_bg_color;
+}
+
+.pane-separator:insensitive {
+    color: @insensitive_internal_element_color;
+}
+
+.pane-separator:selected {
+    color: @selected_internal_element_color;
+}
+
+.pane-separator:selected:hover {
+    color: @theme_selected_fg_color;
+}
+
+.notebook .pane-separator {
+    background-color: @theme_base_color;
+}
+
+/* Calendars */
+GtkCalendar {
+    padding: 2px;
+}
+
+GtkCalendar.view {
+    padding: 2px;
+}
+
+GtkCalendar.header {
+    border-radius: 0;
+    border-style: none;
+    background-color: @theme_bg_color;
+}
+
+GtkCalendar.button,
+GtkCalendar.button:insensitive {
+    background-image: none;
+    background-color: transparent;
+}
+
+.highlight,
+GtkCalendar.highlight {
+    padding: 0;
+    border-radius: 0;
+    border-style: none;
+    background-color: @theme_selected_bg_color;
+    color: @theme_selected_fg_color;
+}
+
+/*******************
+ * Symbolic images *
+ *******************/
+.image,
+.image:focus {
+    color: @internal_element_color;
+}
+
+.image:hover,
+.image:focus:hover {
+    color: @theme_fg_color;
+}
+
+.image:active,
+.image:focus:active {
+    color: @theme_selected_bg_color;
+}
+
+.image:insensitive {
+    color: @insensitive_internal_element_color;
+}
+
+.image:selected {
+    color: @selected_internal_element_color;
+}
+
+.image:selected:hover {
+    color: @theme_selected_fg_color;
+}
+
+.image:insensitive:selected {
+    color: mix(@theme_selected_fg_color, @theme_selected_bg_color, 0.7);
+}
+
+.view.image,
+.view.image:hover {
+    color: @theme_fg_color;
+}
+
+.view.image:selected,
+.view.image:selected:hover {
+    color: @theme_selected_fg_color;
+}
+
+.view.image:insensitive:selected {
+    color: mix(@theme_selected_fg_color, @theme_selected_bg_color, 0.5);
+}
+
+/**************
+ * Dim labels *
+ **************/
+.dim-label,
+.view.dim-label {
+    color: @internal_element_color;
+}
+
+.dim-label:selected,
+.view.dim-label:selected {
+    color: @selected_internal_element_color;
+}
+
+/**********
+ * Frames *
+ **********/
+.frame,
+GtkScrolledWindow.frame {
+    padding: 0;
+    border-width: 1px;
+    border-style: solid;
+    border-color: @border_color;
+}
+
+.frame:insensitive,
+GtkScrolledWindow.frame:insensitive {
+    border-color: @insensitive_border_color;
+}
+
+/* avoid double borders when a viewport is 
+ * packed into a GtkScrolledWindow */
+GtkScrolledWindow GtkViewport.frame {
+    border-style: none;
+}
+
+/**************
+ * GtkInfoBar *
+ **************/
+GtkInfoBar {
+    border-style: none;
+    color: @theme_fg_color;
+}
+
+.info {
+    border-color: mix(shade(@info_color, 0.5), @border_color, 0.5);
+    background-color: mix(@info_color, @theme_bg_color, 0.5);
+}
+
+.warning {
+    border-color: mix(shade(@warning_color, 0.5), @border_color, 0.5);
+    background-color: mix(@warning_color, @theme_bg_color, 0.5);
+}
+
+.question {
+    border-color: mix(shade(@question_color, 0.5), @border_color, 0.5);
+    background-color: mix(@question_color, @theme_bg_color, 0.5);
+}
+
+.error {
+    border-color: mix(shade(@error_color, 0.5), @border_color, 0.5);
+    background-color: mix(@error_color, @theme_bg_color, 0.5);
+}
+
+.other {
+    border-color: mix(shade(@other_color, 0.5), @border_color, 0.5);
+    background-color: mix(@other_color, @theme_bg_color, 0.5);
+}
+
+/*****************
+ * Color Chooser *
+ *****************/
+GtkColorSwatch {
+    border-radius: 0;
+    box-shadow: inset 0 0 0 1px alpha(black, 0.5), inset 0 0 0 1px alpha(@alt_border_color, 0.5);
+    background-color: transparent;
+}
+
+GtkColorSwatch.top {
+    border-top-left-radius: 4px;
+    border-top-right-radius: 4px;
+}
+
+GtkColorSwatch.bottom {
+    border-bottom-right-radius: 4px;
+    border-bottom-left-radius: 4px;
+}
+
+GtkColorSwatch.left,
+GtkColorSwatch:first-child {
+    border-top-left-radius: 4px;
+    border-bottom-left-radius: 4px;
+}
+
+GtkColorSwatch.right,
+GtkColorSwatch:last-child {
+    border-top-right-radius: 4px;
+    border-bottom-right-radius: 4px;
+}
+
+GtkColorSwatch:only-child {
+    border-radius: 4px;
+}
+
+GtkColorEditor GtkColorSwatch {
+    border-radius: 4px;
+}
+
+GtkColorSwatch.color-light:hover {
+    background-image: -gtk-gradient(radial, 0.5 1.5, 0, 0.5 0.5, 1,
+                                    color-stop (0.0, alpha(black, 0.5)),
+                                    color-stop (0.5, alpha(black, 0))
+                                    );
+    background-color: transparent;
+}
+
+GtkColorSwatch.color-dark:hover {
+    background-image: -gtk-gradient(radial, 0.5 1.5, 0, 0.5 0.5, 1,
+                                    color-stop (0.0, alpha(white, 1)),
+                                    color-stop (0.5, alpha(white, 0))
+                                    );
+    background-color: transparent;
+}
+
+GtkColorSwatch.color-light:selected:hover,
+GtkColorSwatch.color-dark:selected:hover {
+    background-image: none;
+}
+
+.color-active-badge,
+.color-active-badge:selected {
+    border-width: 2px;
+    border-style: solid;
+    background-color: transparent;
+}
+
+.color-active-badge.color-light {
+    border-color: black;
+    color: black;
+}
+
+.color-active-badge.color-dark {
+    border-color: white;
+    color: white;
+}
+
+GtkColorChooserWidget #add-color-button {
+    background-image: linear-gradient(to bottom,
+                                      alpha(@theme_base_color, 0.25),
+                                      alpha(@theme_base_color, 0.25)
+                                      ),
+                      linear-gradient(to bottom, alpha(lime, 0.75), alpha(lime, 0)),
+                      linear-gradient(to left, red, blue);
+    background-color: transparent;
+    color: alpha(white, 0.75);
+}
+
+GtkColorChooserWidget #add-color-button:hover {
+    background-image: -gtk-gradient(radial, 0.5 1.5, 0, 0.5 0.5, 1,
+                                    color-stop(0.0, alpha(white, 1)),
+                                    color-stop(0.5, alpha(white, 0))
+                                    ),
+                      linear-gradient(to bottom,
+                                      alpha(@theme_base_color, 0.25),
+                                      alpha(@theme_base_color, 0.25)
+                                      ),
+                      linear-gradient(to bottom, alpha(lime, 0.75), alpha(lime, 0)),
+                      linear-gradient(to left, red, blue);
+    background-color: transparent;
+    color: alpha(white, 0.75);
+}
+
+/****************
+ * Progress bar *
+ ****************/
+GtkProgressBar {
+    -GtkProgressBar-min-horizontal-bar-height: 20;
+    -GtkProgressBar-min-vertical-bar-width: 20;
+
+    padding: 0; /* FIXME: this should really be a margin... */
+}
+
+.progressbar,
+.progressbar row,
+.trough,
+.trough row {
+    border-radius: 100px;
+}
+
+/* for progressbars in cell renderers */
+.trough row {
+    padding: 0;
+}
+
+.trough row:selected {
+    color: @theme_fg_color;
+}
+
+/* level bars as used for password quality or remaining power */
+GtkLevelBar {
+    -GtkLevelBar-min-block-width: 32;
+    -GtkLevelBar-min-block-height: 8;
+}
+
+GtkLevelBar.vertical {
+    -GtkLevelBar-min-block-width: 8;
+    -GtkLevelBar-min-block-height: 32;
+}
+
+.level-bar.trough {
+    padding: 0;
+    border-width: 1px 0;
+    border-color: transparent;
+    background-image: none;
+    background-color: transparent;
+}
+
+.level-bar.indicator-continuous.fill-block {
+    padding: 0;
+}
+
+.level-bar.indicator-discrete.fill-block.horizontal {
+    margin: 0;
+    border-radius: 0;
+    border-left-width: 0;
+}
+
+.level-bar.indicator-discrete.fill-block.vertical {
+    margin: 0;
+    border-radius: 0;
+    border-bottom-width: 0;
+    border-left-width: 1px;
+}
+
+/* discrete indicator border rounding,
+   uncomment when :nth-child will be working
+   on the widget
+
+.level-bar.indicator-discrete.fill-block.horizontal:first-child {
+    border-radius: 100px 0 0 100px;
+    border-left-width: 1px;
+}
+
+.level-bar.indicator-discrete.fill-block.horizontal:last-child {
+    border-radius: 0 100px 100px 0;
+}
+
+.level-bar.indicator-discrete.fill-block.horizontal:only-child {
+    border-radius: 100px;
+    border-left-width: 1px;
+}
+
+.level-bar.indicator-discrete.fill-block.vertical:first-child {
+    border-radius: 100px 100px 0 0;
+}
+
+.level-bar.indicator-discrete.fill-block.vertical:last-child {
+    border-radius: 0 0 100px 100px;
+    border-bottom-width: 1px;
+}
+
+.level-bar.indicator-discrete.fill-block.vertical:only-child {
+    border-radius: 100px;
+    border-bottom-width: 1px;
+}
+
+*/
+
+.level-bar.fill-block {
+    border-radius: 100px;
+    border-width: 1px;
+    border-style: solid;
+    border-color: mix(@alt_border_color, shade(@info_color, 0.2), 0.2);
+    background-image: linear-gradient(to bottom,
+                                      mix(@theme_bg_color, shade(@info_color, 1.1), 0.8),
+                                      alpha(mix(@theme_bg_color, shade(@info_color, 1.1), 0.8), 0) 3px
+                                      ),
+                      linear-gradient(to top,
+                                      mix(@theme_bg_color, shade(@info_color, 0.9), 0.8),
+                                      alpha(mix(@theme_bg_color, shade(@info_color, 0.9), 0.8), 0) 3px
+                                      );
+    background-color: mix(mix(@theme_bg_color, shade(@info_color, 1.1), 0.8), mix(@theme_bg_color, shade(@info_color, 0.9), 0.8), 0.5);
+}
+
+.level-bar.fill-block.level-high {
+    border-color: @suggested_action_border_color;
+    background-image: linear-gradient(to bottom,
+                                      @suggested_action_button_top_color,
+                                      alpha(@suggested_action_button_top_color, 0) 3px
+                                      ),
+                      linear-gradient(to top,
+                                      @suggested_action_button_bottom_color,
+                                      alpha(@suggested_action_button_bottom_color, 0) 3px
+                                      );
+    background-color: mix(@suggested_action_button_top_color, @suggested_action_button_bottom_color, 0.5);
+}
+
+.level-bar.fill-block.level-low {
+    border-color: @destructive_action_border_color;
+    background-image: linear-gradient(to bottom,
+                                      @destructive_action_button_top_color,
+                                      alpha(@destructive_action_button_top_color, 0) 3px
+                                      ),
+                      linear-gradient(to top,
+                                      @destructive_action_button_bottom_color,
+                                      alpha(@destructive_action_button_bottom_color, 0) 3px
+                                      );
+    background-color: mix(@destructive_action_button_top_color, @destructive_action_button_bottom_color, 0.5);
+}
+
+.level-bar.fill-block.empty-fill-block {
+    border-color: @alt_border_color;
+    background-image: linear-gradient(to bottom,
+                                      @button_top_active_color,
+                                      alpha(@button_top_active_color, 0) 3px
+                                      ),
+                      linear-gradient(to top,
+                                      @button_bottom_active_color,
+                                      alpha(@button_bottom_active_color, 0) 3px
+                                      );
+    background-color: mix(@button_top_active_color, @button_bottom_active_color, 0.5);
+}
+
+/************
+ * GtkScale *
+ ************/
+.scale {
+    -GtkScale-slider-length: 24;
+    -GtkRange-slider-width: 24;
+    -GtkRange-trough-border: 0;
+}
+
+.scale.slider {
+    border-radius: 100px;
+}
+
+.scale.scale-has-marks-above.slider.horizontal,
+.scale.scale-has-marks-above.slider.vertical,
+.scale.scale-has-marks-below.slider.horizontal,
+.scale.scale-has-marks-below.slider.vertical {
+    border-style: none;
+}
+
+/* 
+.scale.scale-has-marks-above.slider.horizontal {
+    transform: rotate(45deg);
+    border-top-left-radius: 0;
+}
+
+.scale.scale-has-marks-above.slider.vertical {
+    transform: rotate(45deg);
+    border-bottom-left-radius: 0;
+}
+
+.scale.scale-has-marks-below.slider.horizontal {
+    transform: rotate(45deg);
+    border-bottom-right-radius: 0;
+}
+
+.scale.scale-has-marks-below.slider.vertical {
+    transform: rotate(45deg);
+    border-top-right-radius: 0;
+}
+ */
+
+.scale.trough {
+    margin: 8px 0;
+}
+
+.scale.trough.vertical {
+    margin: 0 8px;
+}
+
+.scale.trough,
+.scale.trough.vertical,
+.scale.trough.highlight,
+.scale.trough.highlight.vertical,
+.scale.progressbar,
+.scale.progressbar.vertical {
+    border-radius: 100px;
+}
+
+.scale.mark.separator {
+    color: @insensitive_border_color;
+}
+
+/**************
+ * Scrollbars *
+ **************/
+.scrollbar {
+    -GtkScrollbar-has-backward-stepper: false;
+    -GtkScrollbar-has-forward-stepper: false;
+    -GtkRange-slider-width: 12px;
+    -GtkScrollbar-min-slider-length: 12px;
+    -GtkRange-trough-border: 0;
+    -GtkRange-stepper-spacing: 0;
+    -GtkRange-trough-under-steppers: 1;
+
+    background-clip: padding-box;
+}
+
+.scrollbar.slider,
+.scrollbar.trough {
+    border-radius: 100px;
+    border-width: 2px;
+    border-style: solid;
+    border-color: transparent;
+}
+
+/* the small square between scrollbars!!! */
+.scrollbars-junction {
+}
+
+.scrollbar.button {
+    border-style: none;
+    background-image: none;
+    background-color: transparent;
+}
+
+.scrollbar.button {
+    color: @internal_element_color;
+}
+
+.scrollbar.button:hover {
+    color: @theme_fg_color;
+}
+
+.scrollbar.button:active {
+    color: @theme_selected_bg_color;
+}
+
+.scrollbar.button:insensitive {
+    color: @insensitive_internal_element_color;
+}
+
+/*************
+ * GtkSwitch *
+ *************/
+GtkSwitch {
+    -GtkSwitch-slider-height: 24;
+}
+
+GtkSwitch.slider {
+    padding: 4px;
+    border-radius: 100px;
+}
+
+/****************
+ * Text Entries *
+ ****************/
+.entry {
+    padding: 4px;
+}
+
+.entry.progressbar,
+.entry.progressbar.pulse {
+    margin: 3px;
+    border-radius: 0;
+    border-style: none;
+    box-shadow: none;
+    background-image: linear-gradient(to top,
+                                      @theme_selected_bg_color 2px,
+                                      transparent 2px
+                                      );
+    background-color: transparent;
+}
+
+.entry:selected {
+    background-color: @theme_selected_bg_color;
+    color: @theme_selected_fg_color;
+}
+
+/* needed for webkit/GtkStyle/Evolution compatibility */
+GtkHTML:active,
+.entry:active {
+    background-color: @subdued_color;
+    color: @theme_fg_color;
+}
+
+.cursor-handle.bottom,
+.cursor-handle.top {
+    border-style: none;
+    /* background-image in assets */
+}
+
+/***********
+ * Buttons *
+ ***********/
+.button {
+    -GtkButton-image-spacing: 4;
+    -GtkButton-interior-focus: true;
+    -GtkButton-default-border: 0;
+    -GtkButton-inner-border: 0;
+}
+
+.button,
+.button.menuitem.menubar,
+GtkColorButton.button {
+    padding: 4px;
+}
+
+.button.text-button {
+    padding: 4px 8px;
+}
+
+.button.image-button {
+    padding: 7px;
+}
+
+.button .separator {
+    color: @insensitive_border_color;
+}
+
+GtkLinkButton.button {
+}
+
+/* Remove borders and backgrounds from primary toolbar buttons */
+.primary-toolbar .button,
+.primary-toolbar .button:insensitive {
+    border-color: transparent;
+    background-image: none;
+    background-color: transparent;
+}
+
+/* linked entries/buttons */
+
+/* Middle button */
+.linked .entry,
+.linked .button,
+.toolbar .button.raised.linked,
+.toolbar .raised.linked .button,
+.primary-toolbar .linked .button,
+.inline-toolbar.toolbar .button,
+.inline-toolbar.toolbar GtkToolButton .button,
+.osd.toolbar .button {
+    border-radius: 0;
+    border-left-width: 0;
+}
+
+/* Leftmost button */
+.linked .entry:first-child,
+.linked .button:first-child,
+.toolbar .button.raised.linked:first-child,
+.toolbar .raised.linked .button:first-child,
+.primary-toolbar .linked .button:first-child,
+.inline-toolbar.toolbar .button:first-child,
+.inline-toolbar.toolbar GtkToolButton:first-child .button,
+.osd.toolbar .button:first-child {
+    border-radius: 4px 0 0 4px;
+    border-left-width: 1px;
+}
+
+/* Rightmost button */
+.linked .entry:last-child,
+.linked .button:last-child,
+.toolbar .button.raised.linked:last-child,
+.toolbar .raised.linked .button:last-child,
+.primary-toolbar .linked .button:last-child,
+.inline-toolbar.toolbar .button:last-child,
+.inline-toolbar.toolbar GtkToolButton:last-child .button,
+.osd.toolbar .button:last-child {
+    border-radius: 0 4px 4px 0;
+}
+
+/* Single button */
+.linked .entry:only-child,
+.linked .button:only-child,
+.toolbar .button.raised.linked:only-child,
+.toolbar .raised.linked .button:only-child,
+.primary-toolbar .linked .button:only-child,
+.inline-toolbar.toolbar .button:only-child,
+.inline-toolbar.toolbar GtkToolButton:only-child .button,
+.osd.toolbar .button:only-child {
+    border-radius: 4px;
+    border-left-width: 1px;
+}
+
+/* Middle button (vertical) */
+.linked.vertical .entry,
+.linked.vertical .button,
+.inline-toolbar.toolbar.vertical .button,
+.inline-toolbar.toolbar.vertical GtkToolButton .button {
+    border-radius: 0;
+    border-bottom-width: 0;
+    border-left-width: 1px;
+}
+
+/* Topmost button (vertical) */
+.linked.vertical .entry:first-child,
+.linked.vertical .button:first-child,
+.inline-toolbar.toolbar.vertical .button:first-child,
+.inline-toolbar.toolbar.vertical GtkToolButton:first-child .button {
+    border-radius: 4px 4px 0 0;
+}
+
+/* Bottommost button (vertical) */
+.linked.vertical .entry:last-child,
+.linked.vertical .button:last-child,
+.inline-toolbar.toolbar.vertical .button:last-child,
+.inline-toolbar.toolbar.vertical GtkToolButton:last-child .button {
+    border-radius: 0 0 4px 4px;
+    border-bottom-width: 1px;
+}
+
+/* Single button (vertical) */
+.linked.vertical .entry:only-child,
+.linked.vertical .button:only-child,
+.inline-toolbar.toolbar.vertical .button:only-child,
+.inline-toolbar.toolbar.vertical GtkToolButton:only-child .button {
+    border-radius: 4px;
+    border-bottom-width: 1px;
+}
+
+/* Special-case GtkMenuToolButton, since it's a GtkToolButton, but it
+ * has a box inside to pack the button and the actual menu */
+.inline-toolbar.toolbar GtkMenuToolButton .button:first-child,
+.inline-toolbar.toolbar GtkMenuToolButton .button:last-child {
+    border-radius: 0;
+    border-left-width: 0;
+}
+
+.inline-toolbar.toolbar GtkMenuToolButton:first-child .button:first-child {
+    border-radius: 4px 0 0 4px;
+    border-left-width: 1px;
+}
+
+.inline-toolbar.toolbar GtkMenuToolButton:last-child .button:last-child {
+    border-radius: 0 4px 4px 0;
+}
+
+.osd.toolbar GtkToolButton .button,
+.osd.toolbar GtkToolButton:first-child .button,
+.osd.toolbar GtkToolButton:last-child .button,
+.osd.toolbar GtkToolButton:only-child .button {
+    border-radius: 4px;
+    border-width: 1px;
+}
+
+/****************
+ * Radiobuttons *
+ ****************/
+.check {
+    border-radius: 0;
+}
+
+.radio {
+    border-radius: 100px;
+}
+
+.check:insensitive,
+.radio:insensitive {
+    background-image: linear-gradient(to bottom, transparent, transparent);
+}
+
+GtkCheckButton:hover,
+GtkCheckButton:active:hover,
+GtkCheckButton:selected {
+    background-color: transparent;
+}
+
+/*****************
+ * GtkSpinButton *
+ *****************/
+.spinbutton .button {
+    padding-right: 6px;
+    padding-left: 7px;
+    border-radius: 0;
+    border-width: 0 0 0 1px;
+}
+
+.spinbutton .button:first-child {
+    border-radius: 4px 0 0 4px;
+}
+
+.spinbutton .button:last-child {
+    border-radius: 0 4px 4px 0;
+}
+
+.spinbutton .button:dir(rtl) {
+    border-width: 0 1px 0 0;
+}
+
+.spinbutton.vertical.entry {
+    border-radius: 0;
+}
+
+.spinbutton.vertical .button {
+    padding: 5px;
+}
+
+.spinbutton.vertical .button:first-child {
+    border-radius: 4px 4px 0 0;
+    border-width: 1px 1px 0 1px;
+}
+
+.spinbutton.vertical .button:last-child {
+    border-radius: 0 0 4px 4px;
+    border-width: 0 1px 1px 1px;
+}
+
+.spinbutton.vertical .button,
+/* :active:active to override the dark overriden style for
+   normal spinbutton buttons, yeah we lack !important */
+.spinbutton.vertical .button:active:active {
+}
+
+/* :insensitive:insensitive to override the dark overriden style for
+   normal spinbutton buttons, yeah we lack !important */
+.spinbutton.vertical .button:insensitive:insensitive {
+}
+
+/**************
+ * ComboBoxes *
+ **************/
+GtkComboBox {
+    -GtkComboBox-arrow-scaling: 0.5;
+    -GtkComboBox-shadow-type: none;
+
+    padding: 0;
+}
+
+GtkComboBox .separator {
+    /* always disable separators */
+    -GtkWidget-wide-separators: true;
+    -GtkWidget-horizontal-separator: 0;
+    -GtkWidget-vertical-separator: 0;
+}
+
+GtkComboBox .button {
+    padding: 2px;
+}
+
+GtkComboBox.combobox-entry .button {
+    padding: 7px;
+}
+
+GtkComboBox.combobox-entry .entry:first-child,
+GtkComboBox.combobox-entry .button:first-child {
+    border-radius: 4px 0 0 4px;
+}
+
+GtkComboBox.combobox-entry .entry:last-child,
+GtkComboBox.combobox-entry .button:last-child {
+    border-radius: 0 4px 4px 0;
+    border-left-width: 0;
+}
+
+/***************
+ * Menu Button *
+ ***************/
+.selection-mode.toolbar .selection-menu.button,
+.selection-mode.header-bar .selection-menu.button {
+    border-color: transparent;
+    background-image: none;
+    background-color: transparent;
+}
+
+.selection-mode.toolbar .selection-menu.button:hover,
+.selection-mode.header-bar .selection-menu.button:hover {
+    border-color: @alt_border_color;
+    background-image: linear-gradient(to bottom,
+                                      @button_top_hover_color,
+                                      @button_bottom_hover_color
+                                      );
+    background-color: transparent;
+}
+
+GtkComboBox.combobox-entry .button:active,
+.linked GtkMenuButton.button:active,
+.toolbar GtkMenuButton.button.raised.linked:active,
+.toolbar .raised.linked GtkMenuButton.button:active,
+.primary-toolbar .linked GtkMenuButton.button:active {
+    border-radius: 0;
+    border-left-width: 0;
+    border-bottom-color: transparent;
+}
+
+GtkComboBox.combobox-entry .button:active:first-child,
+.linked GtkMenuButton.button:active:first-child,
+.toolbar GtkMenuButton.button.raised.linked:active:first-child,
+.toolbar .raised.linked GtkMenuButton.button:active:first-child,
+.primary-toolbar .linked GtkMenuButton.button:active:first-child {
+    border-radius: 4px 0 0 0;
+    border-left-width: 1px;
+}
+
+GtkComboBox.combobox-entry .button:active:last-child,
+.linked GtkMenuButton.button:active:last-child,
+.toolbar GtkMenuButton.button.raised.linked:active:last-child,
+.toolbar .raised.linked GtkMenuButton.button:active:last-child,
+.primary-toolbar .linked GtkMenuButton.button:active:last-child {
+    border-radius: 0 4px 0 0;
+}
+
+.menubar .menuitem:hover, /* remove the image from the prelight areas */
+.button.menuitem.menubar:active,
+GtkMenuButton.button:active,
+.toolbar GtkMenuButton.button.raised:active,
+.toolbar .raised GtkMenuButton.button:active,
+.primary-toolbar GtkMenuButton.button:active,
+.selection-mode.toolbar .selection-menu.button:active,
+.selection-mode.header-bar .selection-menu.button:active,
+.linked GtkMenuButton.button:active:only-child,
+.toolbar GtkMenuButton.button.raised.linked:active:only-child,
+.toolbar .raised.linked GtkMenuButton.button:active:only-child,
+.primary-toolbar .linked GtkMenuButton.button:active:only-child {
+    border-radius: 4px 4px 0 0;
+    border-width: 1px;
+    border-style: solid;
+    border-color: @alt_border_color;
+    border-bottom-color: transparent;
+}
+
+/*********
+ * Menus *
+ *********/
+.context-menu {
+    font: initial;
+}
+
+.menu {
+    padding: 1px;
+    border-width: 1px;
+    border-style: solid;
+    border-color: @alt_border_color;
+    background-color: @theme_base_color;
+    color: @theme_text_color;
+}
+
+.menubar .menu,
+.button.menubar .menu,
+GtkMenuButton .menu,
+.selection-mode.toolbar .selection-menu .menu,
+.selection-mode.header-bar .selection-menu .menu,
+GtkComboBox.combobox-entry .menu {
+    padding-top: 0;
+    border-top-width: 0;
+}
+
+.menu.button,
+.primary-toolbar .menu.button {
+    border-style: none;
+    background-image: none;
+    background-color: @theme_base_color;
+    color: @internal_element_color;
+}
+
+.menu.button:hover,
+.primary-toolbar .menu.button:hover {
+    border-style: none;
+    background-image: none;
+    background-color: @theme_base_color;
+    color: @theme_fg_color;
+}
+
+.menu.button:insensitive,
+.primary-toolbar .menu.button:insensitive {
+    border-style: none;
+    background-image: none;
+    background-color: @theme_base_color;
+    color: @insensitive_internal_element_color;
+}
+
+.menu .menuitem,
+.menubar .menu .menuitem,
+.button .menu .menuitem {
+    -GtkMenuItem-arrow-scaling: 0.5;
+
+    padding: 3px 4px;
+}
+
+.menu .menuitem:hover {
+    border-radius: 0;
+    border-style: none;
+    background-color: @theme_selected_bg_color;
+    color: @theme_selected_fg_color;
+}
+
+.menu .menuitem:insensitive {
+    background-color: @theme_base_color;
+}
+
+.menu .menuitem *:insensitive {
+    color: @insensitive_fg_color;
+}
+
+.menuitem .accelerator {
+    color: @internal_element_color;
+}
+
+.menuitem .accelerator:hover {
+    color: @selected_internal_element_color;
+}
+
+.menuitem .accelerator:insensitive {
+    color: @insensitive_internal_element_color;
+}
+
+.menuitem.arrow {
+    color: @theme_text_color;
+}
+
+.menuitem.arrow:hover {
+    color: @theme_selected_fg_color;
+}
+
+.menuitem.arrow:insensitive {
+    color: @insensitive_fg_color;
+}
+
+.menuitem.separator {
+    -GtkMenuItem-horizontal-padding: 0;
+
+    color: @insensitive_border_color;
+}
+
+.menuitem.check,
+.menuitem.check:active,
+.menuitem.check:insensitive,
+.menuitem.radio,
+.menuitem.radio:active,
+.menuitem.radio:insensitive {
+    box-shadow: none;
+}
+
+.menuitem.check,
+.menuitem.check:hover,
+.menuitem.check:insensitive,
+.menuitem.radio,
+.menuitem.radio:hover,
+.menuitem.radio:insensitive {
+    background-image: none;
+    background-color: transparent;
+}
+
+/* this controls the general appearance of the menubar */
+.menubar {
+    -GtkWidget-window-dragging: true;
+
+    border-style: none;
+}
+
+.menubar .menuitem {
+    padding: 3px 8px;
+}
+
+/* combobox menus */
+GtkTreeMenu.menu {
+    background-color: @theme_base_color;
+}
+
+GtkTreeMenu.menu .menuitem {
+    padding: 4px 2px 2px 2px;
+    color: @theme_text_color;
+}
+
+/**********************
+ * Notebooks and tabs *
+ **********************/
+.notebook {
+    engine: none;
+
+    -GtkNotebook-initial-gap: 4;
+    -GtkNotebook-tab-overlap: 1;
+    -GtkNotebook-tab-curvature: 0;
+
+    padding: 0;
+    border-width: 1px;
+    border-style: solid;
+    border-color: @border_color;
+    background-color: @theme_base_color;
+}
+
+.notebook:insensitive {
+    border-color: @insensitive_border_color;
+}
+
+.notebook GtkViewport {
+    background-color: @theme_base_color;
+}
+
+.notebook tab {
+    padding: 5px 9px;
+}
+
+.notebook tab.top {
+    border-radius: 0;
+    border-width: 1px 1px 0 1px;
+}
+
+.notebook tab:first-child.top {
+    border-top-left-radius: 4px;
+}
+
+.notebook tab:last-child.top {
+    border-top-right-radius: 4px;
+}
+
+.notebook tab.top:active:focus {
+/* 
+    border-image-slice: 1;
+    border-image-source: linear-gradient(to bottom, @focused_border_color, @border_color);
+ */
+    box-shadow: inset 0 1px alpha(@focused_border_color, 0.2),
+                inset -1px 0 alpha(@focused_border_color, 0.1),
+                inset 1px 0 alpha(@focused_border_color, 0.1);
+}
+
+.notebook tab.right {
+    border-radius: 0;
+    border-width: 1px 1px 1px 0;
+}
+
+.notebook tab:first-child.right {
+    border-top-right-radius: 4px;
+}
+
+.notebook tab:last-child.right {
+    border-bottom-right-radius: 4px;
+}
+
+.notebook tab.right:active:focus {
+/* 
+    border-image-slice: 1;
+    border-image-source: linear-gradient(to left, @focused_border_color, @border_color);
+ */
+    box-shadow: inset -1px 0 alpha(@focused_border_color, 0.2),
+                inset 0 1px alpha(@focused_border_color, 0.1),
+                inset 0 -1px alpha(@focused_border_color, 0.1);
+}
+
+.notebook tab.bottom {
+    border-radius: 0;
+    border-width: 0 1px 1px 1px;
+}
+
+.notebook tab:first-child.bottom {
+    border-bottom-left-radius: 4px;
+}
+
+.notebook tab:last-child.bottom {
+    border-bottom-right-radius: 4px;
+}
+
+.notebook tab.bottom:active:focus {
+/* 
+    border-image-slice: 1;
+    border-image-source: linear-gradient(to top, @focused_border_color, @border_color);
+ */
+    box-shadow: inset 0 -1px alpha(@focused_border_color, 0.2),
+                inset -1px 0 alpha(@focused_border_color, 0.1),
+                inset 1px 0 alpha(@focused_border_color, 0.1);
+}
+
+.notebook tab.left {
+    border-radius: 0;
+    border-width: 1px 0 1px 1px;
+}
+
+.notebook tab:first-child.left {
+    border-top-left-radius: 4px;
+}
+
+.notebook tab:last-child.left {
+    border-bottom-left-radius: 4px;
+}
+
+.notebook tab.left:active:focus {
+/* 
+    border-image-slice: 1;
+    border-image-source: linear-gradient(to right, @focused_border_color, @border_color);
+ */
+    box-shadow: inset 1px 0 alpha(@focused_border_color, 0.2),
+                inset 0 1px alpha(@focused_border_color, 0.1),
+                inset 0 -1px alpha(@focused_border_color, 0.1);
+}
+
+/* close button styling */
+.notebook tab .button,
+.notebook tab .button:hover,
+.notebook tab .button:active {
+    border-style: none;
+    background-image: none;
+    background-color: transparent;
+}
+
+.notebook tab .button {
+    color: @internal_element_color;
+}
+
+.notebook tab .button:hover {
+    color: @theme_fg_color;
+}
+
+.notebook tab .button:active {
+    color:  @theme_selected_bg_color;
+}
+
+/************
+ * Toolbars *
+ ************/
+.toolbar {
+    padding: 6px;
+    border-width: 0;
+    border-style: solid;
+    border-color: @border_color;
+    color: @theme_fg_color;
+}
+
+.toolbar:insensitive {
+    color: @insensitive_fg_color;
+}
+
+.toolbar .separator {
+    color: @insensitive_border_color;
+}
+
+.toolbar .button {
+    padding: 3px;
+}
+
+.toolbar .button.text-button {
+    padding: 4px 16px;
+}
+
+.toolbar .button.image-button {
+    padding: 7px;
+}
+
+/********************
+ * Menubar Toolbars *
+ ********************/
+.toolbar.menubar {
+    -GtkToolbar-button-relief: normal;
+}
+
+/***************
+ * Header bars *
+ ***************/
+.header-bar {
+    padding: 6px;
+    border-width: 0 0 1px 0;
+    border-style: solid;
+    border-color: @border_color;
+}
+
+.header-bar:backdrop {
+    color: @internal_element_color;
+}
+
+.header-bar .button.text-button {
+    padding: 4px 16px;
+}
+
+.header-bar .button.image-button {
+    padding: 7px;
+}
+
+.header-bar .title {
+    font: Bold 11;
+}
+
+.header-bar .subtitle {
+    font: 9;
+}
+
+/********************
+ * Primary Toolbars *
+ ********************/
+/* applications using EggEditableToolbar usually set the primary-toolbar
+ * hint on the containing vbox, so we need to handle both these cases here.
+ */
+.primary-toolbar .toolbar,
+.primary-toolbar.toolbar {
+    -GtkWidget-window-dragging: true;
+    -GtkToolbar-button-relief: normal;
+
+    padding-top: 3px;
+    padding-bottom: 3px;
+    border-width: 1px 0;
+    border-style: solid;
+    border-color: @border_color;
+}
+
+/*******************
+ * Inline toolbars *
+ *******************/
+.inline-toolbar.toolbar {
+    -GtkToolbar-button-relief: normal;
+
+    padding: 3px;
+    border-width: 1px;
+    border-style: solid;
+    border-color: @border_color;
+}
+
+.inline-toolbar.toolbar:insensitive {
+    border-color: @insensitive_border_color;
+}
+
+.inline-toolbar.toolbar:last-child {
+    border-top-width: 0;
+}
+
+.inline-toolbar.toolbar.vertical:last-child {
+    border-left-width: 0;
+}
+
+.inline-toolbar.toolbar .button {
+    padding: 7px;
+}
+
+/***********
+ * Sidebar *
+ ***********/
+.sidebar,
+.sidebar .view,
+.sidebar GtkScrolledWindow {
+    background-color: @alt_subdued_color;
+}
+
+.sidebar .view:hover {
+    background-color: @subdued_color;
+}
+
+.sidebar .view:selected {
+    background-color: @theme_selected_bg_color;
+}
+
+.sidebar .view.radio,
+.sidebar .view.radio:active,
+.sidebar .view.radio:focus,
+.sidebar .view.radio:active:focus {
+    box-shadow: none;
+}
+
+.sidebar .view.radio,
+.sidebar .view.radio:selected {
+    background-image: none;
+    background-color: transparent;
+}
+
+/****************
+ * GtkAssistant *
+ ****************/
+GtkAssistant .sidebar {
+    padding: 12px;
+    border-width: 1px;
+    border-style: solid;
+    border-color: @border_color;
+    background-color: @alt_subdued_color;
+    color: @internal_element_color;
+    /* shall we make it like sidebars in general? */
+}
+
+GtkAssistant .sidebar .highlight {
+    background-color: transparent;
+    color: @theme_fg_color;
+    font: bold;
+}
+
+/***********************
+ * Tree and Icon views *
+ ***********************/
+GtkTreeView {
+    -GtkTreeView-vertical-separator: 0;
+}
+
+GtkIconView.cell:selected {
+    /* FIXME: this probably needs to be better;
+     * see https://bugzilla.gnome.org/show_bug.cgi?id=644157
+     */
+    -GtkWidget-focus-padding: 1;
+    -GtkWidget-focus-line-width: 1;
+
+    border-radius: 8px;
+}
+
+GtkTreeView:selected:focus,
+GtkIconView.cell:selected:focus {
+    outline-color: @selected_internal_element_color;
+}
+
+GtkTreeView.cell {
+    padding: 2px;
+}
+
+GtkTreeView.cell:hover {
+    background-color: @alt_base_color;
+    color: @theme_selected_fg_color;
+}
+
+.cell:insensitive {
+    color: @insensitive_fg_color;
+}
+
+.cell:selected, row:selected, .cell:hover:selected {
+    background-color: @theme_selected_bg_color;
+    border-width: 0;
+    border-radius: 3px;
+    color: @theme_selected_fg_color;
+    box-shadow: inset 0px 0px 3px mix(@theme_base_color, @theme_selected_bg_color, 0.4);
+}
+
+.cell:insensitive:selected {
+    color: mix(@theme_selected_fg_color, @theme_selected_bg_color, 0.5);
+}
+
+column-header.button,
+column-header .button {
+    padding: 2px 4px;
+    border-radius: 0;
+    border-width: 0 1px 1px 0;
+}
+
+column-header.button:last-child,
+column-header:last-child .button {
+    border-right-width: 0;
+}
+
+column-header .button.dnd {
+    border-width: 0 1px 1px 1px;
+}
+
+.expander,
+.expander:active {
+    border-color: @internal_element_color;
+    color: @internal_element_color;
+}
+
+.expander:hover,
+.expander:active:hover {
+    border-color: @theme_fg_color;
+    color: @theme_fg_color;
+}
+
+.expander:insensitive,
+.expander:active:insensitive {
+    border-color: @insensitive_internal_element_color;
+    color: @insensitive_internal_element_color;
+}
+
+.expander:focus,
+.expander:active:focus {
+    border-color: @theme_selected_bg_color;
+}
+
+.expander row:selected,
+.expander row:active:selected {
+    border-color: @selected_internal_element_color;
+    color: @selected_internal_element_color;
+}
+
+.expander row:selected:hover,
+.expander row:active:selected:hover {
+    border-color: @theme_selected_fg_color;
+    color: @theme_selected_fg_color;
+}
+
+.expander row:insensitive:selected,
+.expander row:active:insensitive:selected {
+    border-color: mix(@theme_selected_fg_color, @theme_selected_bg_color, 0.7);
+    color: mix(@theme_selected_fg_color, @theme_selected_bg_color, 0.7);
+}
+
+/**************
+ * List boxes *
+ **************/
+.list-row,
+EggListBox {
+    -GtkWidget-focus-padding: 1;
+    -GtkWidget-focus-line-width: 1;
+}
+
+.list,
+EggListBox {
+    background-color: @theme_base_color;
+    color: @theme_text_color;
+}
+
+.list-row:hover,
+EggListBox:hover {
+    background-color: @alt_base_color;
+}
+
+.list-row:insensitive,
+EggListBox:insensitive {
+    background-color: @alt_base_color;
+    color: @insensitive_fg_color;
+}
+
+.list-row:selected,
+EggListBox:selected {
+    background-color: @theme_selected_bg_color;
+    color: @theme_selected_fg_color;
+}
+
+.list-row:selected:focus,
+EggListBox:selected:focus {
+    outline-color: @selected_internal_element_color;
+}
+
+/****************
+ * Content view *
+ ****************/
+/* FIXME: EggListBox should set the .cell style class on
+ * the background it renders for the children, like
+ * GtkIconView and GtkTreeView do */
+.content-view.view,
+.content-view.cell {
+    background-color: @alt_base_color;
+    color: @theme_fg_color;
+}
+
+.content-view.view:hover,
+.content-view.cell:hover,
+EggListBox.content-view:hover {
+    background-color: @theme_bg_color;
+}
+
+.content-view.view:insensitive,
+.content-view.cell:insensitive,
+EggListBox.content-view:insensitive {
+    color: @insensitive_fg_color;
+}
+
+.content-view.view:active,
+.content-view.view:selected,
+.content-view.cell:active,
+.content-view.cell:selected,
+EggListBox.content-view:active,
+EggListBox.content-view:selected {
+    background-image: none;
+    background-color: @theme_selected_bg_color;
+    color: @theme_selected_fg_color;
+}
+
+GdMainIconView.content-view {
+    -GdMainIconView-icon-size: 40;
+}
+
+GtkIconView.content-view.cell.check {
+    opacity: 0.9;
+    background-size: contain;
+}
+
+GtkIconView.content-view.check:hover,
+GtkIconView.content-view.check:insensitive,
+GtkIconView.content-view.check:selected {
+    background-color: transparent;
+}
+
+.content-view.view.check,
+.content-view.view.check:active {
+    background-color: transparent;
+}
+
+.content-view.view .separator {
+    color: @insensitive_border_color;
+}
+
+/* used by Documents and Evince */
+.content-view.document-page {
+    border-image: url("assets/thumbnail-frame.png") 3 3 6 4;
+    border-width: 3px 3px 6px 4px;
+    border-style: solid;
+}
+
+/************
+ * Tooltips *
+ ************/
+.tooltip {
+    opacity: 0.9;
+    padding: 5px;
+    border-radius: 4px;
+    background-color: alpha(@theme_tooltip_bg_color, 0.9);
+    color: alpha(@theme_tooltip_fg_color, 0.9);
+}
+
+/*****************
+ * Floating bars *
+ *****************/
+.floating-bar {
+    opacity: 0.9;
+    padding: 3px;
+    border-radius: 4px;
+    background-color: @theme_tooltip_bg_color;
+    color: @theme_tooltip_fg_color;
+}
+
+.floating-bar.top {
+    border-top-left-radius: 0;
+    border-top-right-radius: 0;
+}
+
+.floating-bar.right {
+    border-top-right-radius: 0;
+    border-bottom-right-radius: 0;
+}
+
+.floating-bar.bottom {
+    border-bottom-right-radius: 0;
+    border-bottom-left-radius: 0;
+}
+
+.floating-bar.left {
+    border-top-left-radius: 0;
+    border-bottom-left-radius: 0;
+}
+
+/*********************
+ * App Notifications *
+ *********************/
+.app-notification {
+    opacity: 0.9;
+    padding: 8px;
+    border-radius: 0 0 8px 8px;
+    background-color: @theme_tooltip_bg_color;
+    color: @theme_tooltip_fg_color;
+}
+
+/*******
+ * OSD *
+ *******/
+.osd {
+    opacity: 0.9;
+}
+
+.background.osd {
+    background-color: @theme_tooltip_bg_color;
+    color: @theme_tooltip_fg_color;
+}
+
+GtkOverlay.osd {
+    background-color: transparent;
+}
+
+.osd.frame {
+    background-clip: border-box;
+    background-origin: border-box;
+}
+
+.osd .button {
+    padding: 7px;
+}
+
+.osd.button {
+    padding: 11px;
+}
+
+.osd.toolbar {
+    -GtkToolbar-button-relief: normal;
+
+    padding: 8px;
+    border-radius: 8px;
+    border-style: none;
+    background-image: linear-gradient(to bottom,
+                                      mix(@theme_tooltip_bg_color, @subdued_color, 0.2),
+                                      mix(@theme_tooltip_bg_color, @border_color, 0.2)
+                                      );
+    background-color: transparent;
+    color: @theme_tooltip_fg_color;
+}
+
+.osd.toolbar .separator {
+    color: @insensitive_border_color;
+}
+
+/* used by gnome-settings-daemon's media-keys OSD */
+.osd.progressbar {
+    background-color: @theme_selected_bg_color;
+}
+
+.osd.trough {
+    background-color: mix(@button_top_active_color, @button_bottom_active_color, 0.5);
+}
+
+.osd GtkProgressBar,
+GtkProgressBar.osd {
+    -GtkProgressBar-xspacing: 0;
+    -GtkProgressBar-yspacing: 4;
+    -GtkProgressBar-min-horizontal-bar-height: 4;
+
+    padding: 0;
+}
+
+.osd GtkProgressBar.progressbar,
+GtkProgressBar.osd.progressbar {
+    border-radius: 0;
+    border-style: none;
+    background-image: none;
+    background-color: @theme_selected_bg_color;
+}
+
+.osd GtkProgressBar.trough,
+GtkProgressBar.osd.trough {
+    padding: 0;
+    border-radius: 0;
+    border-style: none;
+    background-image: none;
+    background-color: transparent;
+}
+
+.osd .view,
+.osd.view {
+    background-color: @theme_tooltip_bg_color;
+    color: @theme_tooltip_fg_color;
+}
+
+/* used by Documents */
+.osd .page-thumbnail {
+    border-width: 1px;
+    border-style: solid;
+    border-color: @border_color;
+    /* when there's no pixbuf yet */
+    background-color: @theme_tooltip_bg_color;
+}
+
+/*************************
+ * Touch text selections *
+ *************************/
+GtkBubbleWindow {
+    border-radius: 8px;
+}
+
+GtkBubbleWindow.osd.background {
+    background-color: @theme_tooltip_bg_color;
+}
+
+GtkBubbleWindow .toolbar {
+    background-color: transparent;
+}
+
+/*******
+ * CSD *
+ *******/
+.titlebar {
+    background-image: linear-gradient(to bottom,
+                                      @button_top_color,
+                                      @theme_bg_color);
+    border-radius: 7px 7px 0px 0px;
+}
+
+/* this is the default titlebar that is added by GTK
+ * when client-side decorations are in use and the application
+ * did not set a custom titlebar.
+ */
+.titlebar.default-decoration {
+    border: none;
+    box-shadow: none;
+}
+
+.titlebar .title {
+    font: Bold 11;
+}
+
+.titlebar:backdrop {
+    text-shadow: none;
+    background-image: none;
+    background-color: @theme_bg_color;
+}
+
+.titlebar .titlebutton {
+    color: @theme_fg_color;
+}
+
+.titlebar .titlebutton:hover {
+}
+
+.titlebar .titlebutton:active {
+}
+
+.titlebar .titlebutton:backdrop {
+    color: @internal_element_color;
+}
+
+.titlebar .titlebutton:backdrop:hover {
+    color: @theme_fg_color;
+}
+
+.titlebar .right .titlebutton:first-child {
+/*    border-left: 1px solid @menu_separator; */
+}
+
+.titlebar .right .titlebutton:last-child {
+
+}
+
+.titlebar .left .titlebutton:last-child {
+/*    border-right: 1px solid @menu_separator; */
+}
+
+.titlebar .left .titlebutton:first-child {
+
+}
+
+.window-frame {
+    border-radius: 7px 7px 0 0;
+    border-width: 1px;
+    border-style: solid;
+    border-color: @border_color;
+    box-shadow: 0 2px 8px 2px alpha(black, 0.5), 0 0 0 2px @border_color;
+
+    /* this is used for the resize cursor area */
+    margin: 10px;
+}
+
+.window-frame:backdrop {
+    box-shadow: 0 2px 4px 0 alpha(black, 0.5), 0 0 0 2px @border_color;
+}
+
+/**********************
+ * Fallback Mode Panel
+ **********************/
+.gnome-panel-menu-bar,
+PanelApplet > GtkMenuBar.menubar,
+PanelToplevel,
+PanelWidget,
+PanelAppletFrame,
+PanelApplet {
+    background-image: none;
+    background-color: @os_chrome_bg_color;
+    color: @os_chrome_fg_color;
+}
+
+ClockBox,
+.gnome-panel-menu-bar.menubar,
+PanelApplet > GtkMenuBar.menubar {
+    font: bold;
+}
+
+.gnome-panel-menu-bar.menubar .menuitem:hover,
+PanelApplet > GtkMenuBar.menubar .menuitem:hover {
+    text-shadow: 0 1px @os_chrome_bg_color;
+}
+
+.gnome-panel-menu-bar.menubar .menu,
+PanelApplet > GtkMenuBar.menubar .menu {
+    font: regular;
+}
+
+.gnome-panel-menu-bar.menubar .menu:hover,
+PanelApplet > GtkMenuBar.menubar .menu:hover {
+    text-shadow: none;
+}
+
+.gnome-panel-menu-bar .menuitem:hover,
+PanelApplet > GtkMenuBar.menubar .menuitem:hover {
+    background-color: @os_chrome_selected_bg_color;
+    color: @os_chrome_selected_fg_color;
+}
+
+PanelApplet .button,
+PanelApplet .button:hover {
+    padding: 4px;
+
+    border-radius: 0;
+    border-image: none;
+    border-width: 0;
+
+    background-image: none;
+    background-color: @os_chrome_bg_color;
+
+    color: @os_chrome_fg_color;
+    text-shadow: none;
+}
+
+PanelApplet .button:active:hover,
+PanelApplet .button:active {
+    border-radius: 0;
+    border-image: none;
+    border-width: 0;
+    background-image: none;
+    background-color: @os_chrome_selected_bg_color;
+}
+
+PanelApplet:hover {
+    color: @os_chrome_selected_fg_color;
+}
+
+PanelApplet:active,
+PanelApplet:hover:active {
+    color: @os_chrome_selected_fg_color;
+    text-shadow: 0 1px @os_chrome_bg_color;
+}
+
+WnckPager {
+    background-color: lighter(@os_chrome_selected_bg_color);
+}
+
+NaTrayApplet {
+    -NaTrayApplet-icon-padding: 12;
+    -NaTrayApplet-icon-size: 16;
+}
+
+GsmFailWhaleDialog {
+    background-image: none;
+    background-color: @os_chrome_bg_color;
+    color: @os_chrome_fg_color;
+}
+
+GsmFailWhaleDialog .button,
+GsmFailWhaleDialog .button:active {
+    border-image: none;
+    border-width: 1px;
+    border-color: @alt_border_color;
+}
+
+GtkSourceView {
+    background-color: @theme_bg_color;
+    color: @theme_fg_color;
+    font: monospace;
+}
+
diff --git a/app-data/themes/gtk/default/gtk.css b/app-data/themes/gtk/default/gtk.css
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/gtk.css
@@ -0,0 +1,31 @@
+/* Default color scheme */
+@define-color theme_base_color #ffffff;
+@define-color theme_text_color #2e3436;
+
+@define-color border_color #888a85;
+@define-color internal_element_color #888a85;
+
+@define-color theme_selected_bg_color #4e9a06;
+@define-color theme_selected_fg_color #36342e;
+
+@define-color link_color #729fcf;
+@define-color info_color #c4a000;
+@define-color warning_color #f57900;
+@define-color question_color #ad7fa8;
+@define-color error_color #ef2929;
+@define-color other_color #c17d11;
+
+@define-color subdued_color shade(@theme_base_color, 0.7);
+@define-color theme_bg_color mix(@theme_base_color, @subdued_color, 0.7);
+@define-color theme_fg_color @theme_text_color;
+@define-color theme_tooltip_bg_color mix(@subdued_color, #000000, 0.5);
+@define-color theme_tooltip_fg_color @theme_text_color;
+
+@define-color placeholder_text_color @internal_element_color;
+
+@import url("gtk-common.css");
+@import url("gtk-widgets-assets.css");
+@import url("gtk-widgets-backgrounds-and-texts.css");
+@import url("gtk-widgets-borders.css");
+@import url("gtk-widgets.css");
+@import url("gnome-applications.css");
diff --git a/app-data/themes/gtk/default/keybindings.css b/app-data/themes/gtk/default/keybindings.css
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/keybindings.css
@@ -0,0 +1,10 @@
+
+@binding-set treeViewExpander
+{
+    bind "Left" { "expand-collapse-cursor-row" (1, 0, 0) };
+    bind "Right" { "expand-collapse-cursor-row" (1, 1, 0) };
+}
+
+GtkTreeView {
+    gtk-key-bindings: treeViewExpander;   
+}
diff --git a/app-data/themes/gtk/default/reset.css b/app-data/themes/gtk/default/reset.css
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtk/default/reset.css
@@ -0,0 +1,63 @@
+/* from http://ruby-doc.org/gems/docs/g/gtk3-2.2.0/sample/gtk-demo/reset_css.html */
+
+* {
+  color: inherit;
+  font-size: inherit;
+  background-color: initial;
+  font-family: inherit;
+  font-style: inherit;
+  font-variant: inherit;
+  font-weight: inherit;
+  text-shadow: inherit;
+  icon-shadow: inherit;
+  box-shadow: initial;
+  margin-top: initial;
+  margin-left: initial;
+  margin-bottom: initial;
+  margin-right: initial;
+  padding-top: initial;
+  padding-left: initial;
+  padding-bottom: initial;
+  padding-right: initial;
+  border-top-style: initial;
+  border-top-width: initial;
+  border-left-style: initial;
+  border-left-width: initial;
+  border-bottom-style: initial;
+  border-bottom-width: initial;
+  border-right-style: initial;
+  border-right-width: initial;
+  border-top-left-radius: initial;
+  border-top-right-radius: initial;
+  border-bottom-right-radius: initial;
+  border-bottom-left-radius: initial;
+  outline-style: initial;
+  outline-width: initial;
+  outline-offset: initial;
+  background-clip: initial;
+  background-origin: initial;
+  background-size: initial;
+  background-position: initial;
+  border-top-color: initial;
+  border-right-color: initial;
+  border-bottom-color: initial;
+  border-left-color: initial;
+  outline-color:  initial;
+  background-repeat: initial;
+  background-image: initial;
+  border-image-source: initial;
+  border-image-repeat: initial;
+  border-image-slice: initial;
+  border-image-width: initial;
+  transition-property: initial;
+  transition-duration: initial;
+  transition-timing-function: initial;
+  transition-delay: initial;
+  engine: initial;
+  gtk-key-bindings: initial;
+
+  -GtkWidget-focus-line-width: 0;
+  -GtkWidget-focus-padding: 0;
+  -GtkNotebook-initial-gap: 0;
+}
+
diff --git a/app-data/themes/gtksourceview/molokai.xml b/app-data/themes/gtksourceview/molokai.xml
new file mode 100644
--- /dev/null
+++ b/app-data/themes/gtksourceview/molokai.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" ?>
+<style-scheme id="molokai" name="Molokai" version="1.0">
+	<author/>
+	<_description>
+		Port of the Molokai Text Mate Theme to GTKSourceView. 
+	</_description>
+	<style foreground="#AE81FF" name="def:boolean"/>
+	<style foreground="#AE81FF" name="def:number"/>
+	<style foreground="#E6DB74" name="def:string"/>
+	<style bold="true" foreground="#AE81FF" name="def:constant"/>
+	<style background="#F8F8F0" foreground="#000000" name="cursor"/>
+	<style background="#13354A" name="diff:added-line"/>
+	<style background="#4C4745" foreground="#89807D" name="diff:changed-line"/>
+	<style background="#1E0010" foreground="#960050" name="diff:removed-line"/>
+	<style background="#1E0010" foreground="#960050" name="def:error"/>
+	<style foreground="#A6E22E" name="def:function"/>
+	<style foreground="#FD971F" name="def:identifier"/>
+	<style bold="true" foreground="#F92672" name="def:keyword"/>
+	<style background="#FD971F" bold="true" foreground="#000000" name="bracket-match"/>
+	<style foreground="#A6E22E" name="def:preprocessor"/>
+	<style background="#455354" foreground="#FFFFFF" name="search-match"/>
+	<style background="#bg" foreground="#66D9EF" italic="true" name="def:specials"/>
+	<style bold="true" foreground="#F92672" name="def:statement"/>
+	<style foreground="#66D9EF" name="def:type"/>
+	<style background="#272822" foreground="#F8F8F2" name="text"/>
+	<style foreground="#75715E" name="def:comment"/>
+	<style background="#3E3D32" name="current-line"/>
+	<style background="#272822" foreground="#F8F8F2" name="text"/>
+	<style foreground="#465457" name="def:comment"/>
+	<style background="#3e3d32" name="current-line"/>
+	<style background="#3b3a32" foreground="#bcbca7" name="line-numbers"/>
+</style-scheme>
+
diff --git a/app-data/ui.glade b/app-data/ui.glade
new file mode 100644
--- /dev/null
+++ b/app-data/ui.glade
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.16.1 -->
+<interface>
+  <requires lib="gtk+" version="3.10"/>
+  <object class="GtkWindow" id="mainWindow">
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkPaned" id="sidebarPaned">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkBox" id="box2">
+            <property name="width_request">170</property>
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkScrolledWindow" id="sidebarScroller">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkTreeView" id="directoryListing">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <child internal-child="selection">
+                      <object class="GtkTreeSelection" id="treeview-selection1"/>
+                    </child>
+                    <style>
+                      <class name="sidebar"/>
+                    </style>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="directoryListingSearch">
+                <property name="can_focus">True</property>
+                <property name="hexpand">True</property>
+                <property name="invisible_char">●</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="resize">False</property>
+            <property name="shrink">True</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <object class="GtkNotebook" id="tabbedEditArea">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="hexpand">True</property>
+                <property name="vexpand">True</property>
+                <property name="show_border">False</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="box6">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <object class="GtkEntry" id="command">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="invisible_char">●</property>
+                    <property name="caps_lock_warning">False</property>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="activeMode">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="pack_type">end</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="resize">True</property>
+            <property name="shrink">True</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/hob.cabal b/hob.cabal
new file mode 100644
--- /dev/null
+++ b/hob.cabal
@@ -0,0 +1,71 @@
+name: hob
+version: 0.0.1.0
+cabal-version: >=1.10
+build-type: Simple
+license: GPL-3
+license-file: LICENSE
+copyright: (c) Sarunas Valaskevicius
+maintainer: Sarunas Valaskevicius <rakatan@gmail.com>
+synopsis: A source code editor aiming for the convenience of use
+description: A simple command based code editor being developed for the mouse-free workflow.
+homepage: http://svalaskevicius.github.io/hob/
+stability: alpha
+bug-reports: http://github.com/svalaskevicius/hob/issues
+category: IDE
+author: Sarunas Valaskevicius
+data-dir: app-data
+data-files: ui.glade,
+            themes/gtksourceview/*.xml,
+            themes/gtk/default/*.css,
+            themes/gtk/default/assets/*.png,
+            themes/gtk/default/assets/*.svg
+
+source-repository head
+    type: git
+    location: https://github.com/svalaskevicius/hob
+
+executable hob
+    build-depends: base >=4.2 && <5, gtk3 >=0.13.0.1, mtl >=2.1.0 && <2.2.0,
+                   directory -any, containers -any, filepath -any, hob, bytestring -any, text >= 1.1.1.0 && < 1.1.2.0,
+                   gtksourceview3 >=0.13.0.0
+    main-is: hob.hs
+    buildable: True
+    default-language: Haskell2010
+    ghc-options:
+        -Wall
+    hs-source-dirs: src
+
+library
+    Exposed-modules:     Hob.Ui, Hob.Ui.Editor, Hob.Ui.Sidebar, Hob.Ui.CommandEntry,
+                         Hob.Ui.SidebarSearch, Hob.Ui.Editor.Search,
+                         Hob.Command.FindText, Hob.Command.ReplaceText, Hob.Command.CloseCurrentTab,
+                         Hob.Command.SaveCurrentTab, Hob.Command.NewTab, Hob.Command.FocusCommandEntry,
+                         Hob.Command.FocusNextTab, Hob.Command.ReloadSidebarTree, Hob.Command.FocusSidebar,
+                         Hob.Command.FocusPreviousTab, Hob.Command.FocusNumberedTab,
+                         Hob.Context, Hob.Context.StyleContext, Hob.Context.FileContext, Hob.Context.UiContext,
+                         Hob.Context.Types, Hob.Context.Events, Hob.Context.CommandMatcher, Hob.Context.Editor,
+                         Hob.DirectoryTree, Hob.Control
+    default-language:    Haskell2010
+    ghc-options:
+        -Wall
+    hs-source-dirs:      src/lib
+    build-depends: base >=4.7 && <5, containers -any,
+                   filepath -any, glib >=0.13.0.0, gtk3 >=0.13.0.1, pango >= 0.13.0.0,
+                   gtksourceview3 >=0.13.0.0, mtl >=2.1.0 && <2.2.0,
+                   transformers >=0.3.0.0 && <0.4, text >= 1.1.1.0,
+                   system-filepath -any, gtk-largeTreeStore >= 0.0.1.0, vector >= 0.10.12.2 && < 0.10.13
+
+test-suite spec
+  type:
+      exitcode-stdio-1.0
+  default-language: Haskell2010
+  ghc-options:
+      -Wall -Werror
+  hs-source-dirs:
+      test
+  main-is:
+      Spec.hs
+  other-modules: Hob.Ui.SidebarSpec, Hob.Ui.EditorSpec, Hob.Ui.CommandEntrySpec, Hob.Ui.Editor.SearchSpec, Hob.Ui.SidebarSearchSpec, Hob.UiSpec, Hob.ContextSpec, Hob.ControlSpec, Hob.DirectoryTreeSpec, Hob.Command.FocusNextTabSpec, Hob.Command.FindTextSpec, Hob.Command.CloseCurrentTabSpec, Hob.Command.FocusSidebarSpec, Hob.Command.SaveCurrentTabSpec, Hob.Command.ReplaceTextSpec, Hob.Command.FocusCommandEntrySpec, Hob.Command.NewTabSpec, Hob.Command.FocusPreviousTabSpec, Hob.Command.FocusNumberedTabSpec, Hob.Command.ReloadSidebarTreeSpec, Hob.Context.FileContextSpec, Hob.Context.StyleContextSpec, HobTest.Sidebar, HobTest.Editor, HobTest.Context.Stubbed, HobTest.Context.Default, HobTest.Control
+  build-depends:
+      base    >= 4.2 && <5, containers -any, mtl >=2.1.0 && <2.2.0,
+      hspec   >= 1.11, gtk3 >=0.13.0.1, hob, text >= 1.1.1.0, gtksourceview3 >=0.13.0.0
diff --git a/src/hob.hs b/src/hob.hs
new file mode 100644
--- /dev/null
+++ b/src/hob.hs
@@ -0,0 +1,55 @@
+module Main (main) where
+
+import           Control.Monad.Trans (liftIO)
+import qualified Data.ByteString     as BS
+import qualified Data.Text.Encoding  as E
+import           Graphics.UI.Gtk
+import           System.Directory
+import           System.Environment  (getArgs)
+import           System.IO           (hPutStr, stderr)
+
+import Hob.Context
+import Hob.Context.FileContext  (FileLoader, FileTreeLoader, FileWriter,
+                                 defaultFileContext)
+import Hob.Context.StyleContext (defaultStyleContext)
+import Hob.Context.UiContext
+import Hob.DirectoryTree
+import Hob.Ui
+
+import Paths_hob
+
+main :: IO ()
+main = do
+    fileCtx <- getFileContext
+    styleCtx <- getStyleContext
+    ctx <- loadGui fileCtx styleCtx
+    let window = mainWindow . uiContext $ ctx
+    _ <- window `on` deleteEvent $ liftIO mainQuit >> return False
+    widgetShow window
+    mainGUI
+    where
+         getFileContext = do
+             projectRoot <- getProjectDirectory
+             defaultFileContext loadFile storeFile (fileTreeFromDirectory projectRoot)
+         getStyleContext = defaultStyleContext =<< getDataDir
+         getProjectDirectory = do
+             args <- getArgs
+             case args of
+                 [] -> getCurrentDirectory
+                 [path] -> canonicalizePath path
+                 _ -> error "unsupported command options"
+
+
+fileTreeFromDirectory :: FilePath -> FileTreeLoader
+fileTreeFromDirectory = fileTreeGenerator getDirectoryContents doesDirectoryExist
+
+loadFile :: FileLoader
+loadFile path = do
+        contents <- BS.readFile path
+        checkError $ E.decodeUtf8' contents
+        where checkError (Left e) = do hPutStr stderr (show e)
+                                       return Nothing
+              checkError (Right t) = return $ Just t
+
+storeFile :: FileWriter
+storeFile path = BS.writeFile path . E.encodeUtf8
diff --git a/src/lib/Hob/Command/CloseCurrentTab.hs b/src/lib/Hob/Command/CloseCurrentTab.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Command/CloseCurrentTab.hs
@@ -0,0 +1,24 @@
+module Hob.Command.CloseCurrentTab (closeCurrentEditorTab) where
+
+
+import Control.Monad.Trans (liftIO)
+import Graphics.UI.Gtk
+
+import Hob.Context
+import Hob.Context.UiContext
+
+closeCurrentEditorTab :: CommandHandler
+closeCurrentEditorTab = CommandHandler Nothing closeCurrentEditorTabHandler
+
+closeCurrentEditorTabHandler :: App()
+closeCurrentEditorTabHandler = do
+    ui <- fromContext uiContext
+    let tabbed = mainNotebook ui
+    currentPage <- liftIO $ notebookGetCurrentPage tabbed
+    nthPage <- liftIO $ notebookGetNthPage tabbed currentPage
+    case nthPage of
+        Just pageContents -> do
+            liftIO $ notebookRemovePage tabbed currentPage
+            liftIO $ widgetDestroy pageContents
+        Nothing -> return ()
+
diff --git a/src/lib/Hob/Command/FindText.hs b/src/lib/Hob/Command/FindText.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Command/FindText.hs
@@ -0,0 +1,54 @@
+module Hob.Command.FindText (
+        searchCommandHandler,
+        searchNextCommandHandler,
+        searchBackwardsCommandHandler,
+        searchPreview,
+        searchResetPreview,
+        searchMode,
+    ) where
+
+import Data.Monoid     (mconcat)
+import Graphics.UI.Gtk
+
+import Hob.Context
+import Hob.Ui.Editor
+import Hob.Ui.Editor.Search
+
+searchCommandHandler :: String -> CommandHandler
+searchCommandHandler searchText = CommandHandler (Just $ PreviewCommandHandler (searchPreview searchText) searchResetPreview) (searchStart searchText)
+
+searchNextCommandHandler :: CommandHandler
+searchNextCommandHandler = CommandHandler Nothing searchNext
+
+searchBackwardsCommandHandler :: CommandHandler
+searchBackwardsCommandHandler = CommandHandler Nothing searchPrevious
+
+searchPreview :: String -> App()
+searchPreview text = invokeOnActiveEditor (`highlightSearchPreview` text)
+
+searchReset :: App()
+searchReset = invokeOnActiveEditor resetSearch
+
+searchResetPreview :: App()
+searchResetPreview = invokeOnActiveEditor resetSearchPreview
+
+searchNext :: App()
+searchNext = invokeOnActiveEditor findNext
+
+searchPrevious :: App()
+searchPrevious = invokeOnActiveEditor findPrevious
+
+searchStart :: String -> App()
+searchStart text = do
+    enterMode searchMode
+    invokeOnActiveEditor $ \editor -> do
+        findFirstFromCursor editor text
+        widgetGrabFocus editor
+
+searchMode :: Mode
+searchMode = Mode "search" matcher searchReset
+    where matcher = mconcat [
+                              createMatcherForKeyBinding ([Control], "Down") searchNextCommandHandler,
+                              createMatcherForKeyBinding ([Control], "Up") searchBackwardsCommandHandler]
+
+
diff --git a/src/lib/Hob/Command/FocusCommandEntry.hs b/src/lib/Hob/Command/FocusCommandEntry.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Command/FocusCommandEntry.hs
@@ -0,0 +1,47 @@
+module Hob.Command.FocusCommandEntry (
+    toggleFocusOnCommandEntryCommandHandler,
+    focusActiveEditorAndExitLastModeCommandHandler,
+    focusCommandEntryCommandHandler,
+    ) where
+
+import Control.Monad.Reader
+import Graphics.UI.Gtk
+
+import Hob.Context
+import Hob.Context.UiContext
+import Hob.Control
+import Hob.Ui.Editor
+
+focusCommandEntryCommandHandler :: CommandHandler
+focusCommandEntryCommandHandler = CommandHandler Nothing focusCommandEntry
+
+focusActiveEditorAndExitLastModeCommandHandler :: CommandHandler
+focusActiveEditorAndExitLastModeCommandHandler = CommandHandler Nothing focusActiveEditorAndExitLastMode
+
+toggleFocusOnCommandEntryCommandHandler :: CommandHandler
+toggleFocusOnCommandEntryCommandHandler = CommandHandler Nothing toggleFocusOnCommandEntry
+
+
+focusCommandEntry :: App()
+focusCommandEntry = do
+    ctx <- ask
+    liftIO $ widgetGrabFocus $ cmdEntry ctx
+    where cmdEntry ctx = commandEntry.uiContext $ ctx
+
+focusActiveEditorAndExitLastMode :: App()
+focusActiveEditorAndExitLastMode = do
+    exitLastMode
+    ctx <- ask
+    editor <- liftIO $ getActiveEditor ctx
+    liftIO $ maybeDo widgetGrabFocus editor
+
+toggleFocusOnCommandEntry :: App()
+toggleFocusOnCommandEntry = do
+    ctx <- ask
+    isFocused <- liftIO $ widgetGetIsFocus $ cmdEntry ctx
+    if isFocused then do
+        editor <- liftIO $ getActiveEditor ctx
+        liftIO $ maybeDo widgetGrabFocus editor
+    else
+        liftIO $ widgetGrabFocus $ cmdEntry ctx
+    where cmdEntry ctx = commandEntry.uiContext $ ctx
diff --git a/src/lib/Hob/Command/FocusNextTab.hs b/src/lib/Hob/Command/FocusNextTab.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Command/FocusNextTab.hs
@@ -0,0 +1,22 @@
+module Hob.Command.FocusNextTab (focusNextTabCommandHandler) where
+
+import Control.Monad.Trans (liftIO)
+import Graphics.UI.Gtk
+
+import Hob.Context
+import Hob.Context.UiContext
+
+focusNextTabCommandHandler :: CommandHandler
+focusNextTabCommandHandler = CommandHandler Nothing focusNextTab
+
+focusNextTab :: App()
+focusNextTab = do
+    ui <- fromContext uiContext
+    let notebook = mainNotebook ui
+    pages <- liftIO $ notebookGetNPages notebook
+    currentPage <- liftIO $ notebookGetCurrentPage notebook
+    liftIO $ notebookSetCurrentPage notebook (nextPage currentPage pages)
+    where
+        nextPage currentPage pages =
+            if currentPage == (pages - 1) then 0 else currentPage + 1
+
diff --git a/src/lib/Hob/Command/FocusNumberedTab.hs b/src/lib/Hob/Command/FocusNumberedTab.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Command/FocusNumberedTab.hs
@@ -0,0 +1,18 @@
+module Hob.Command.FocusNumberedTab (focusNumberedTabCommandHandler) where
+
+import Control.Monad       (when)
+import Control.Monad.Trans (liftIO)
+import Graphics.UI.Gtk
+
+import Hob.Context
+import Hob.Context.UiContext
+
+focusNumberedTabCommandHandler :: Int -> CommandHandler
+focusNumberedTabCommandHandler nr = CommandHandler Nothing $ focusNumberedTab nr
+
+focusNumberedTab :: Int -> App()
+focusNumberedTab nr = do
+    ui <- fromContext uiContext
+    let notebook = mainNotebook ui
+    pages <- liftIO $ notebookGetNPages notebook
+    when (nr >= 0 && nr < pages) $ liftIO $ notebookSetCurrentPage notebook nr
diff --git a/src/lib/Hob/Command/FocusPreviousTab.hs b/src/lib/Hob/Command/FocusPreviousTab.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Command/FocusPreviousTab.hs
@@ -0,0 +1,22 @@
+module Hob.Command.FocusPreviousTab (focusPreviousTabCommandHandler) where
+
+import Control.Monad.Trans (liftIO)
+import Graphics.UI.Gtk
+
+import Hob.Context
+import Hob.Context.UiContext
+
+focusPreviousTabCommandHandler :: CommandHandler
+focusPreviousTabCommandHandler = CommandHandler Nothing focusPreviousTab
+
+focusPreviousTab :: App()
+focusPreviousTab = do
+    ui <- fromContext uiContext
+    let notebook = mainNotebook ui
+    pages <- liftIO $ notebookGetNPages notebook
+    currentPage <- liftIO $ notebookGetCurrentPage notebook
+    liftIO $ notebookSetCurrentPage notebook (previousPage currentPage pages)
+    where
+        previousPage currentPage pages =
+            if currentPage == 0 then pages - 1 else currentPage - 1
+
diff --git a/src/lib/Hob/Command/FocusSidebar.hs b/src/lib/Hob/Command/FocusSidebar.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Command/FocusSidebar.hs
@@ -0,0 +1,37 @@
+module Hob.Command.FocusSidebar (
+    focusSidebarCommandHandler,
+    syncFocusSidebarCommandHandler
+    ) where
+
+import Control.Monad.Reader
+import Graphics.UI.Gtk
+
+import Hob.Context
+import Hob.Context.UiContext
+import Hob.Control
+import Hob.Ui.Editor         (getActiveEditor, getEditorFilePath)
+import Hob.Ui.Sidebar        (syncPathToSidebar)
+
+focusSidebarCommandHandler :: CommandHandler
+focusSidebarCommandHandler = CommandHandler Nothing focusSidebar
+
+syncFocusSidebarCommandHandler :: CommandHandler
+syncFocusSidebarCommandHandler = CommandHandler Nothing syncFocusSidebar
+
+focusSidebar :: App()
+focusSidebar = do
+    ctx <- ask
+    liftIO $ widgetGrabFocus . sidebarTree . uiContext $ ctx
+
+syncActiveEditorPathToSidebar :: App()
+syncActiveEditorPathToSidebar = do
+    ctx <- ask
+    editor <- liftIO $ getActiveEditor ctx
+    maybeDo syncToEditor editor
+    where syncToEditor editor = maybeDo syncPathToSidebar =<< liftIO (getEditorFilePath editor)
+
+
+syncFocusSidebar :: App()
+syncFocusSidebar = syncActiveEditorPathToSidebar >> focusSidebar
+
+
diff --git a/src/lib/Hob/Command/NewTab.hs b/src/lib/Hob/Command/NewTab.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Command/NewTab.hs
@@ -0,0 +1,51 @@
+module Hob.Command.NewTab (
+            launchNewFileEditor,
+            editNewFile,
+            editNewFileCommandHandler,
+            NewFileEditorLauncher) where
+
+
+import Control.Monad.Reader
+import Data.Maybe           (mapMaybe)
+import Data.Text            (pack)
+import Graphics.UI.Gtk
+
+import Hob.Context
+import Hob.Context.FileContext
+import Hob.Context.UiContext
+import Hob.Control
+import Hob.Ui.Editor
+
+type NewFileEditorLauncher = FilePath -> IO ()
+
+editNewFileCommandHandler :: CommandHandler
+editNewFileCommandHandler = CommandHandler Nothing editNewFile
+
+launchNewFileEditor :: Context -> Notebook -> NewFileEditorLauncher
+launchNewFileEditor ctx targetNotebook filePath = do
+    let fileLoader = contextFileLoader . fileContext $ ctx
+    currentEditors <- mapM getEditorFromNotebookTab <=< containerGetChildren $ targetNotebook
+    editorsForFile <- filterM (\(_, ed) -> isEditorFileMatching ed ) $ numberedJusts currentEditors
+    case alreadyLoadedPage editorsForFile of
+        Just nr -> notebookSetCurrentPage targetNotebook nr
+        Nothing -> maybeDo launchEditor =<< fileLoader filePath
+
+    where launchEditor text = deferredRunner ctx $ newEditorForText targetNotebook (Just filePath) text
+          isEditorFileMatching editor = do
+              f <- getEditorFilePath editor
+              return $ maybe False (filePath ==) f
+          alreadyLoadedPage [(nr, _)] = Just nr
+          alreadyLoadedPage _ = Nothing
+
+editNewFile :: App()
+editNewFile = do
+    ctx <- ask
+    let tabbed = mainNotebook.uiContext $ ctx
+    newEditorForText tabbed Nothing $ pack ""
+
+liftTupledMaybe :: (a, Maybe b) -> Maybe (a, b)
+liftTupledMaybe (x, Just y) = Just (x, y)
+liftTupledMaybe (_, Nothing) = Nothing
+
+numberedJusts :: [Maybe a] -> [(Int, a)]
+numberedJusts a = mapMaybe liftTupledMaybe $ zip [0..] a
diff --git a/src/lib/Hob/Command/ReloadSidebarTree.hs b/src/lib/Hob/Command/ReloadSidebarTree.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Command/ReloadSidebarTree.hs
@@ -0,0 +1,11 @@
+module Hob.Command.ReloadSidebarTree (reloadSidebarTreeCommandHandler) where
+
+import Hob.Context
+import Hob.Ui.Sidebar
+
+reloadSidebarTreeCommandHandler :: CommandHandler
+reloadSidebarTreeCommandHandler = CommandHandler Nothing reloadCmd
+
+reloadCmd :: App()
+reloadCmd = reloadSidebarTree
+
diff --git a/src/lib/Hob/Command/ReplaceText.hs b/src/lib/Hob/Command/ReplaceText.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Command/ReplaceText.hs
@@ -0,0 +1,67 @@
+module Hob.Command.ReplaceText (
+        createMatcherForReplace,
+        replaceCommandHandler,
+        replaceNextCommandHandler,
+    ) where
+
+import Data.Monoid     (mconcat)
+import Graphics.UI.Gtk
+
+import Hob.Command.FindText
+import Hob.Context
+import Hob.Ui.Editor
+import Hob.Ui.Editor.Search
+
+createMatcherForReplace :: Char -> (String -> String -> CommandHandler) -> CommandMatcher
+createMatcherForReplace prefix handler = CommandMatcher (const Nothing) match
+    where match [] = Nothing
+          match (p:ps)
+           | p == prefix = matchSearchAndReplaceFromStart ps
+           | otherwise = Nothing
+
+          matchSearchAndReplaceFromStart [] = Nothing
+          matchSearchAndReplaceFromStart (separator:xs) = matchSearchAndReplaceFromSearch separator xs ""
+
+          matchSearchAndReplaceFromSearch _ [] _ = Nothing
+          matchSearchAndReplaceFromSearch separator (x:xs) accumSearch
+           | '\\' == x = case xs of
+                            [] -> Nothing
+                            (s:xss) -> if s == separator then matchSearchAndReplaceFromSearch separator xss (accumSearch++[separator])
+                                       else matchSearchAndReplaceFromSearch separator xs (accumSearch++[x])
+           | separator == x = matchSearchAndReplaceFromReplace separator xs accumSearch ""
+           | otherwise = matchSearchAndReplaceFromSearch separator xs (accumSearch++[x])
+
+          matchSearchAndReplaceFromReplace _ [] search accumReplace = Just $ handler search accumReplace
+          matchSearchAndReplaceFromReplace separator (x:xs) search accumReplace
+           | '\\' == x = case xs of
+                            [] -> Just $ handler search (accumReplace++[x])
+                            (s:xss) -> if s == separator then matchSearchAndReplaceFromReplace separator xss search (accumReplace++[separator])
+                                       else matchSearchAndReplaceFromReplace separator xs search (accumReplace++[x])
+           | separator == x = Just $ handler search accumReplace
+           | otherwise = matchSearchAndReplaceFromReplace separator xs search (accumReplace++[x])
+
+replaceCommandHandler :: String -> String -> CommandHandler
+replaceCommandHandler searchText replaceText = CommandHandler (Just $ PreviewCommandHandler (searchPreview searchText) searchResetPreview) (replaceStart searchText replaceText)
+
+replaceNextCommandHandler :: CommandHandler
+replaceNextCommandHandler = CommandHandler Nothing invokeReplaceNext
+
+replaceStart :: String -> String -> App()
+replaceStart searchText replaceText = do
+    enterMode replaceMode
+    invokeOnActiveEditor $ \editor -> do
+        startReplace editor searchText replaceText
+        widgetGrabFocus editor
+
+invokeReplaceNext :: App()
+invokeReplaceNext = invokeOnActiveEditor replaceNext
+
+replaceReset :: App()
+replaceReset = invokeOnActiveEditor resetReplace
+
+replaceMode :: Mode
+replaceMode = Mode "replace" matcher replaceReset
+    where matcher = mconcat [ commandMatcher searchMode
+                            , createMatcherForKeyBinding ([Shift, Control], "Down") replaceNextCommandHandler
+                            ]
+
diff --git a/src/lib/Hob/Command/SaveCurrentTab.hs b/src/lib/Hob/Command/SaveCurrentTab.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Command/SaveCurrentTab.hs
@@ -0,0 +1,59 @@
+module Hob.Command.SaveCurrentTab (
+        NewFileNameChooser,
+        saveCurrentEditorTab,
+        saveCurrentEditorTabHandler) where
+
+import Control.Monad.Reader
+import Graphics.UI.Gtk
+import Graphics.UI.Gtk.SourceView (SourceView)
+
+import Hob.Context
+import Hob.Context.FileContext
+import Hob.Context.UiContext
+import Hob.Control
+import Hob.Ui.Editor
+
+type NewFileNameChooser = IO (Maybe FilePath)
+
+saveCurrentEditorTab :: CommandHandler
+saveCurrentEditorTab = CommandHandler Nothing (do
+    ctx <- ask
+    saveCurrentEditorTabHandler $ fileChooser ctx)
+    where
+        fileChooser :: Context -> IO (Maybe FilePath)
+        fileChooser ctx = do
+            dialog <- fileChooserDialogNew Nothing (Just $ mainWindow.uiContext $ ctx) FileChooserActionSave [("Cancel", ResponseCancel), ("Save", ResponseOk)]
+            resp <- dialogRun dialog
+            file <- if resp == ResponseOk then fileChooserGetFilename dialog else return Nothing
+            widgetDestroy dialog
+            return file
+
+saveCurrentEditorTabHandler :: NewFileNameChooser -> App()
+saveCurrentEditorTabHandler newFileNameChooser = do
+    ctx <- ask
+    maybeEditor <- liftIO $ getActiveEditor ctx
+    maybeDo (saveInContext newFileNameChooser) maybeEditor
+
+
+saveInContext :: NewFileNameChooser -> SourceView -> App ()
+saveInContext newFileNameChooser editor = do
+    ctx <- ask
+    liftIO $ saveEditor ctx
+    where fileWriter ctx = contextFileWriter . fileContext $ ctx
+          saveEditor ctx = do
+              path <- getEditorFilePath editor
+              case path of
+                  Just filePath -> saveEditorContents ctx filePath
+                  Nothing -> askForFile $ saveAsNewFile ctx
+          askForFile onSuccess = newFileNameChooser >>= maybe (return()) onSuccess
+          saveAsNewFile ctx filePath = do
+              saveEditorContents ctx filePath
+              setEditorFilePath editor $ Just filePath
+              updateEditorTitle editor
+
+          saveEditorContents ctx filePath = do
+              textBuf <- textViewGetBuffer editor
+              text <- get textBuf textBufferText
+              fileWriter ctx filePath text
+              textBuf `set` [textBufferModified := False]
+              return ()
diff --git a/src/lib/Hob/Context.hs b/src/lib/Hob/Context.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Context.hs
@@ -0,0 +1,85 @@
+module Hob.Context (
+    App,
+    Context(..),
+    PreviewCommandHandler(..),
+    CommandHandler(..),
+    CommandMatcher(..),
+    KeyboardBinding,
+    KeyCommandMatcher,
+    TextCommandMatcher,
+    Mode(..),
+    Event(..),
+    Editor(..),
+    EditorList(..),
+    initContext,
+    runOnEditor,
+    enterMode,
+    exitLastMode,
+    activeModes,
+    deferredRunner,
+    registerEventHandler,
+    emitEvent,
+    createMatcherForPrefix,
+    createMatcherForCommand,
+    createMatcherForKeyBinding,
+    fromContext,
+    runApp,
+    getActiveCommands,
+) where
+
+import Control.Concurrent.MVar
+import Control.Monad.Reader
+import Graphics.UI.Gtk          (postGUIAsync)
+import GtkExtras.LargeTreeStore as LTS (TreeStore)
+
+import Hob.Context.CommandMatcher
+import Hob.Context.Editor
+import Hob.Context.Events
+import Hob.Context.FileContext
+import Hob.Context.StyleContext
+import Hob.Context.Types
+import Hob.Context.UiContext
+import Hob.DirectoryTree
+
+
+
+initContext :: StyleContext -> FileContext -> UiContext -> LTS.TreeStore DirectoryTreeElement -> CommandMatcher -> IO Context
+initContext styleCtx fileCtx uiCtx treeModel initCommands = do
+    actionSync <- newMVar True
+    deferredMessagesRef <- newMVar []
+    bus <- initEventBus
+    editorList <- initEditors
+    initialisedIdGenerator <- initIdGenerator
+    let ctx = Context styleCtx fileCtx uiCtx treeModel initCommands editorList (messageRunner actionSync deferredMessagesRef) bus initialisedIdGenerator
+    return ctx
+    where
+        messageRunner actionSync deferredMessagesRef ctx message = do
+            queueMessage deferredMessagesRef message
+            sync <- tryTakeMVar actionSync
+            case sync of
+                Just _ -> flushMessageQueue deferredMessagesRef ctx >> putMVar actionSync True
+                Nothing -> return()
+        flushMessageQueue deferredMessagesRef ctx = do
+            messages <- swapMVar deferredMessagesRef []
+            unless (null messages) $ foldM runMessage ctx messages >>= flushMessageQueue deferredMessagesRef
+        queueMessage deferredMessagesRef message = modifyMVar_ deferredMessagesRef (\messages -> return $ messages ++ [message])
+
+runApp :: Context -> App a -> IO a
+runApp ctx appSteps = runReaderT appSteps ctx
+
+runMessage :: Context -> Message -> IO Context
+runMessage  ctx (AppAction action) = runApp ctx action >> return ctx
+
+deferredRunner :: Context -> App() -> IO()
+deferredRunner ctx actions = postGUIAsync $ messageLoop ctx ctx $ AppAction actions
+
+initIdGenerator :: IO (IO Int)
+initIdGenerator = do
+    generatorStorage <- newMVar 0
+    return $ do
+        lastGeneratdId <- takeMVar generatorStorage
+        let newId = lastGeneratdId + 1
+        putMVar generatorStorage newId
+        return newId
+
+
diff --git a/src/lib/Hob/Context/CommandMatcher.hs b/src/lib/Hob/Context/CommandMatcher.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Context/CommandMatcher.hs
@@ -0,0 +1,33 @@
+module Hob.Context.CommandMatcher (
+    createMatcherForPrefix,
+    createMatcherForCommand,
+    createMatcherForKeyBinding,
+) where
+
+import Hob.Context.Types
+
+
+createMatcherForPrefix :: String -> (String -> CommandHandler) -> CommandMatcher
+createMatcherForPrefix prefix handler = CommandMatcher (const Nothing) (matchHandler prefix)
+    where
+        matchHandler :: String -> String -> Maybe CommandHandler
+        matchHandler (p:xs) (t:xt) = if p == t then matchHandler xs xt else Nothing
+        matchHandler "" params = Just $ handler params
+        matchHandler _ "" = Nothing
+
+createMatcherForCommand :: String -> CommandHandler -> CommandMatcher
+createMatcherForCommand command handler = CommandMatcher (const Nothing) (matchHandler command)
+    where
+        matchHandler :: String -> String -> Maybe CommandHandler
+        matchHandler (p:xs) (t:xt) = if p == t then matchHandler xs xt else Nothing
+        matchHandler "" "" = Just handler
+        matchHandler _ "" = Nothing
+        matchHandler "" _ = Nothing
+
+createMatcherForKeyBinding :: KeyboardBinding -> CommandHandler -> CommandMatcher
+createMatcherForKeyBinding keyBinding handler = CommandMatcher (matchHandler keyBinding) (const Nothing)
+    where
+        matchHandler :: KeyboardBinding -> KeyboardBinding -> Maybe CommandHandler
+        matchHandler boundKey matchedKey = if boundKey == matchedKey then Just handler else Nothing
+
+
diff --git a/src/lib/Hob/Context/Editor.hs b/src/lib/Hob/Context/Editor.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Context/Editor.hs
@@ -0,0 +1,90 @@
+module Hob.Context.Editor (
+    initEditors,
+    runOnEditor,
+    enterMode,
+    exitLastMode,
+    activeModes,
+    getActiveCommands,
+    ) where
+
+import Control.Concurrent.MVar
+import Control.Monad.Reader
+import Data.Monoid
+
+import Hob.Context.Events
+import Hob.Context.Types
+
+
+runOnEditor :: (Editor -> Editor -> a) -> Editor -> a
+runOnEditor f editor = f editor editor
+
+initEditors :: IO EditorList
+initEditors = do
+    editorList <- newMVar []
+    return $ EditorList (updateEditorsHandler editorList) (getEditorsHandler editorList)
+    where
+        updateEditorsHandler editorList updater = do
+            oldEditors <- takeMVar editorList
+            newEditors <- updater oldEditors
+            putMVar editorList newEditors
+        getEditorsHandler = readMVar
+
+currentEditor :: App (Maybe Editor)
+currentEditor = do
+    editorList <- fromContext editors
+    active <- filterM (\e -> isCurrentlyActive e e) =<< liftIO (getEditors editorList)
+    return $
+        if null active then Nothing
+        else Just $ head active
+
+enterMode :: Mode -> App()
+enterMode mode = do
+    updateActiveEditor $ \editor -> do
+        editor' <- clearEditorModes editor
+        runOnEditor enterEditorMode editor' mode
+    emitEvent $ Event "core.mode.change"
+    where clearEditorModes editor = do
+              modes <- runOnEditor modeStack editor
+              if not . null $ modes then do
+                  editor' <- runOnEditor exitLastEditorMode editor
+                  clearEditorModes editor'
+              else return editor
+
+activeModes :: App (Maybe [Mode])
+activeModes = do
+    active <- currentEditor
+    maybe (return Nothing) (runOnEditor modeStack >=> return . Just) active
+
+exitLastMode :: App()
+exitLastMode = do
+    updateActiveEditor $ \editor -> runOnEditor exitLastEditorMode editor
+    emitEvent $ Event "core.mode.change"
+
+updateActiveEditor :: (Editor -> App Editor) -> App()
+updateActiveEditor actions = do
+    ctx <- ask
+    editorList <- fromContext editors
+    liftIO $ updateEditors editorList $ \oldEditors -> runReaderT (updateActiveEditorHandler oldEditors) ctx
+    where
+        updateActiveEditorHandler oldEditors = do
+            (e1, e2) <- splitBeforeFirstActive oldEditors
+            if null e2 then return oldEditors
+            else do
+                let active = head e2
+                active' <- actions active
+                return $ e1 ++ [active'] ++ tail e2
+        splitBeforeFirstActive [] = return ([], [])
+        splitBeforeFirstActive (x:xs) = do
+            active <- runOnEditor isCurrentlyActive x
+            if active then return ([], x:xs)
+            else do
+                (n, ns) <- splitBeforeFirstActive xs
+                return (x:n, ns)
+
+getActiveCommands :: App CommandMatcher
+getActiveCommands = do
+    base <- asks baseCommands
+    maybeModes <- activeModes
+    maybe (return base)
+          (\modes -> return $ base `mappend` mconcat (fmap commandMatcher modes))
+          maybeModes
diff --git a/src/lib/Hob/Context/Events.hs b/src/lib/Hob/Context/Events.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Context/Events.hs
@@ -0,0 +1,45 @@
+module Hob.Context.Events (
+    initEventBus,
+    registerEventHandler,
+    emitEvent,
+) where
+
+import Control.Concurrent.MVar
+import Control.Monad.Reader
+
+import Hob.Context.Types
+
+initEventBus :: IO EventBus
+initEventBus = do
+    bus <- newMVar []
+    return $ EventBus (addEventListener bus) (lookupEvent bus)
+    where
+        addEventListener bus event action = do
+            listeners <- takeMVar bus
+            putMVar bus $ combineEventListeners event action listeners
+
+        combineEventListeners :: Event -> App() -> [(Event, [App()])] -> [(Event, [App()])]
+        combineEventListeners event action [] = [(event, [action])]
+        combineEventListeners event action (x@(evt, initHandlers):xs) =
+            if evt == event then (evt, action:initHandlers) : xs
+            else x : combineEventListeners event action xs
+
+        lookupEvent bus event = do
+            listeners <- readMVar bus
+            return $ findEvent event listeners
+
+        findEvent _ [] = []
+        findEvent event ((evt, handlers):xs) = if evt == event then handlers
+                                               else findEvent event xs
+
+registerEventHandler :: Event -> App() -> App()
+registerEventHandler event handler = do
+    bus <- fromContext eventBus
+    liftIO $ addListener bus event handler
+
+emitEvent :: Event -> App()
+emitEvent event = do
+    bus <- fromContext eventBus
+    handlers <- liftIO $ listenersForEvent bus event
+    sequence_ handlers
+
diff --git a/src/lib/Hob/Context/FileContext.hs b/src/lib/Hob/Context/FileContext.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Context/FileContext.hs
@@ -0,0 +1,35 @@
+module Hob.Context.FileContext (
+    FileContext(..),
+    FileLoader,
+    FileWriter,
+    FileTreeLoader,
+    defaultFileContext,
+    sourceLanguage
+) where
+
+import Data.Text                  (Text)
+import Data.Tree                  (Forest)
+import Graphics.UI.Gtk.SourceView (SourceLanguage, SourceLanguageManager,
+                                   sourceLanguageManagerGuessLanguage,
+                                   sourceLanguageManagerNew)
+
+import Hob.DirectoryTree
+
+type FileLoader = FilePath -> IO (Maybe Text)
+type FileWriter = FilePath -> Text -> IO ()
+type FileTreeLoader = IO (Forest DirectoryTreeElement)
+
+data FileContext = FileContext {
+    contextLanguageManager :: SourceLanguageManager,
+    contextFileLoader      :: FileLoader,
+    contextFileWriter      :: FileWriter,
+    contextFileTreeLoader  :: FileTreeLoader
+}
+
+defaultFileContext :: FileLoader -> FileWriter -> FileTreeLoader -> IO FileContext
+defaultFileContext loader writer treeLoader = do
+    languageManager <- sourceLanguageManagerNew
+    return $ FileContext languageManager loader writer treeLoader
+
+sourceLanguage :: FileContext -> FilePath -> IO (Maybe SourceLanguage)
+sourceLanguage ctx filePath = sourceLanguageManagerGuessLanguage (contextLanguageManager ctx) (Just filePath) (Nothing::Maybe String)
diff --git a/src/lib/Hob/Context/StyleContext.hs b/src/lib/Hob/Context/StyleContext.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Context/StyleContext.hs
@@ -0,0 +1,43 @@
+module Hob.Context.StyleContext (
+    StyleContext,
+    defaultStyleContext,
+    uiFile,
+    uiTheme,
+    sourceStyleScheme,
+    sourceStyleFont
+) where
+
+import Control.Monad                 (liftM)
+import Graphics.Rendering.Pango.Font (FontDescription,
+                                      fontDescriptionFromString)
+import Graphics.UI.Gtk.SourceView    (SourceStyleScheme,
+                                      SourceStyleSchemeManager,
+                                      sourceStyleSchemeManagerGetDefault,
+                                      sourceStyleSchemeManagerGetScheme,
+                                      sourceStyleSchemeManagerSetSearchPath)
+import System.FilePath               ((</>))
+
+data StyleContext = StyleContext {
+    contextDataPath     :: FilePath,
+    contextStyleManager :: SourceStyleSchemeManager
+}
+
+defaultStyleContext :: FilePath -> IO StyleContext
+defaultStyleContext dataDir = do
+    styleManager <- sourceStyleSchemeManagerGetDefault
+    sourceStyleSchemeManagerSetSearchPath styleManager (Just [dataDir </> "themes" </> "gtksourceview"])
+    return $ StyleContext dataDir styleManager
+
+uiFile :: StyleContext -> FilePath
+uiFile ctx = contextDataPath ctx </> "ui.glade"
+
+uiTheme :: StyleContext -> FilePath
+uiTheme ctx = contextDataPath ctx </> "themes" </> "gtk" </> "default" </> "gtk-dark.css"
+
+sourceStyleScheme :: StyleContext -> Maybe FilePath -> IO (Maybe SourceStyleScheme)
+sourceStyleScheme ctx _ = do
+    style <- sourceStyleSchemeManagerGetScheme (contextStyleManager ctx) "molokai"
+    return $ Just style
+
+sourceStyleFont :: StyleContext -> Maybe FilePath -> IO (Maybe FontDescription)
+sourceStyleFont _ _ = liftM Just $ fontDescriptionFromString "monospace 12"
diff --git a/src/lib/Hob/Context/Types.hs b/src/lib/Hob/Context/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Context/Types.hs
@@ -0,0 +1,110 @@
+{-# LANGUAGE MultiParamTypeClasses #-}
+
+module Hob.Context.Types (
+    App,
+    EventBus(..),
+    Context(..),
+    PreviewCommandHandler(..),
+    CommandHandler(..),
+    CommandMatcher(..),
+    Message(..),
+    KeyboardBinding,
+    KeyCommandMatcher,
+    TextCommandMatcher,
+    Mode(..),
+    Event(..),
+    Editor(..),
+    EditorList(..),
+    fromContext,
+    ) where
+
+import Control.Monad.Reader
+import Data.Maybe               (isJust)
+import Data.Monoid
+import Graphics.UI.Gtk          (Modifier)
+import GtkExtras.LargeTreeStore as LTS (TreeStore)
+
+import Hob.Context.FileContext
+import Hob.Context.StyleContext
+import Hob.Context.UiContext
+import Hob.DirectoryTree
+
+
+type App = ReaderT Context IO
+
+newtype Event = Event String deriving (Eq, Show)
+
+data Editor = Editor {
+    editorId           :: Editor -> App Int,
+    enterEditorMode    :: Editor -> Mode -> App Editor,
+    exitLastEditorMode :: Editor -> App Editor,
+    modeStack          :: Editor -> App [Mode],
+    isCurrentlyActive  :: Editor -> App Bool
+}
+
+data EventBus = EventBus {
+    addListener       :: Event -> App() -> IO(),
+    listenersForEvent :: Event -> IO [App()]
+}
+
+data EditorList = EditorList {
+    updateEditors :: ([Editor] -> IO [Editor]) -> IO(),
+    getEditors    :: IO [Editor]
+}
+
+data Context = Context {
+    styleContext  :: StyleContext,
+    fileContext   :: FileContext,
+    uiContext     :: UiContext,
+    fileTreeStore :: LTS.TreeStore DirectoryTreeElement,
+    baseCommands  :: CommandMatcher,
+    editors       :: EditorList,
+    messageLoop   :: Context -> Message -> IO(),
+    eventBus      :: EventBus,
+    idGenerator   :: IO Int
+}
+
+data Mode = Mode {
+    modeName       :: String,
+    commandMatcher :: CommandMatcher,
+    cleanup        :: App()
+}
+
+data Message = AppAction (App())
+
+
+data PreviewCommandHandler = PreviewCommandHandler {
+                    previewExecute :: App(),
+                    previewReset   :: App()
+                }
+
+data CommandHandler = CommandHandler {
+                    commandPreview :: Maybe PreviewCommandHandler,
+                    commandExecute :: App()
+                }
+
+type KeyboardBinding = ([Modifier], String)
+type SingleCommandMatcher a = a -> Maybe CommandHandler
+type KeyCommandMatcher = SingleCommandMatcher KeyboardBinding
+type TextCommandMatcher = SingleCommandMatcher String
+
+data CommandMatcher = CommandMatcher {
+                    matchKeyBinding :: KeyCommandMatcher,
+                    matchCommand    :: TextCommandMatcher
+                }
+
+instance Monoid CommandMatcher where
+    mempty = CommandMatcher (const Nothing) (const Nothing)
+    mappend l r = CommandMatcher (combineMatchKeyBinding l r) (combineMatchCommand l r)
+        where combineMatchKeyBinding = combineMatcher matchKeyBinding
+              combineMatchCommand = combineMatcher matchCommand
+
+
+fromContext :: MonadReader r m => (r -> a) -> m a
+fromContext = asks
+
+
+combineMatcher :: (CommandMatcher -> SingleCommandMatcher a) -> CommandMatcher -> CommandMatcher -> SingleCommandMatcher a
+combineMatcher combiner l r cmd = if isJust rightResult then rightResult else leftResult
+    where leftResult = combiner l cmd
+          rightResult = combiner r cmd
diff --git a/src/lib/Hob/Context/UiContext.hs b/src/lib/Hob/Context/UiContext.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Context/UiContext.hs
@@ -0,0 +1,14 @@
+module Hob.Context.UiContext (
+    UiContext(..)
+) where
+
+import Graphics.UI.Gtk (Entry, Label, Notebook, TreeView, Window)
+
+data UiContext = UiContext {
+    mainWindow        :: Window,
+    mainNotebook      :: Notebook,
+    commandEntry      :: Entry,
+    sidebarTree       :: TreeView,
+    sidebarTreeSearch :: Entry,
+    activeModesLabel  :: Label
+}
diff --git a/src/lib/Hob/Control.hs b/src/lib/Hob/Control.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Control.hs
@@ -0,0 +1,15 @@
+module Hob.Control (
+    maybeDo,
+    flushEvents
+) where
+
+import Control.Monad   (when)
+import Graphics.UI.Gtk (eventsPending, mainIteration)
+
+maybeDo :: Monad b => (a -> b ()) -> Maybe a -> b ()
+maybeDo = maybe (return())
+
+flushEvents :: IO()
+flushEvents = do
+    pending <- eventsPending
+    when (pending > 0) $ mainIteration >> flushEvents
diff --git a/src/lib/Hob/DirectoryTree.hs b/src/lib/Hob/DirectoryTree.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/DirectoryTree.hs
@@ -0,0 +1,54 @@
+module Hob.DirectoryTree (
+    DirectoryForest,
+    DirectoryTreeLoader,
+    DirectoryReader,
+    IsDirectoryCheck,
+    DirectoryTreeElement(..),
+    fileTreeGenerator
+) where
+
+import Control.Monad   (forM)
+import Data.List       (sortBy)
+import Data.Tree
+import System.FilePath
+
+type IsDirectory = Bool
+
+data DirectoryTreeElement = DirectoryTreeElement { elementLabel :: String, elementPath :: FilePath, isDirectory :: IsDirectory }
+
+instance Show (DirectoryTreeElement) where
+  show (DirectoryTreeElement label path isDir) = show label ++" ["++ show path ++"] "++(if isDir then "(directory)" else "(file)")
+
+instance  Eq (DirectoryTreeElement)  where
+    (DirectoryTreeElement label path isDir) == (DirectoryTreeElement label' path' isDir') = (label == label') && (path == path') && (isDir == isDir')
+
+type DirectoryForest = Forest DirectoryTreeElement
+type DirectoryTreeLoader = FilePath -> IO DirectoryForest
+type DirectoryReader = FilePath -> IO [FilePath]
+type IsDirectoryCheck = FilePath -> IO Bool
+
+fileTreeGenerator :: DirectoryReader -> IsDirectoryCheck -> DirectoryTreeLoader
+fileTreeGenerator getDirectoryContents doesDirectoryExist root = do
+    directoryContents <- retrieveDirectoryContents
+    forM directoryContents $ \ (name, path, isDir) -> do
+        childrenForest <- if isDir then callSelf path else return []
+        return $ Node (DirectoryTreeElement name path isDir) childrenForest
+    where
+        callSelf = fileTreeGenerator getDirectoryContents doesDirectoryExist
+        removeBannedDirectories = filter (`notElem` bannedDirectories)
+        addFileInfo contents =
+            forM contents $ \ child -> do
+                let childPath = root </> child
+                isDir <- doesDirectoryExist childPath
+                return (child, childPath, isDir)
+        retrieveDirectoryContents = do
+            contents <- getDirectoryContents root
+            let filteredContents = removeBannedDirectories contents
+            filteredFileInfo <- addFileInfo filteredContents
+            return $ sortBy contentsSortCriteria filteredFileInfo
+        bannedDirectories = [".", "..", ".git"]
+        contentsSortCriteria (name1, _, isDir1) (name2, _, isDir2)
+            | isDir1 && not isDir2 = LT
+            | isDir2 && not isDir1 = GT
+            | otherwise = compare name1 name2
+
diff --git a/src/lib/Hob/Ui.hs b/src/lib/Hob/Ui.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Ui.hs
@@ -0,0 +1,150 @@
+module Hob.Ui (loadGui,
+               getActiveEditorText,
+               getActiveEditorTab,
+               getEditorText,
+               getActiveEditor) where
+
+import           Control.Monad.Reader
+import           Data.Char                            (intToDigit)
+import           Data.IORef
+import           Data.List                            (intercalate)
+import           Data.Monoid                          (mconcat)
+import           Data.Text                            (unpack)
+import           Graphics.UI.Gtk
+import           Graphics.UI.Gtk.General.CssProvider
+import qualified Graphics.UI.Gtk.General.StyleContext as GtkSc
+import           GtkExtras.LargeTreeStore             as LTS
+
+import Hob.Command.CloseCurrentTab
+import Hob.Command.FindText
+import Hob.Command.FocusCommandEntry
+import Hob.Command.FocusNextTab
+import Hob.Command.FocusNumberedTab
+import Hob.Command.FocusPreviousTab
+import Hob.Command.FocusSidebar
+import Hob.Command.NewTab
+import Hob.Command.ReloadSidebarTree
+import Hob.Command.ReplaceText
+import Hob.Command.SaveCurrentTab
+import Hob.Context
+import Hob.Context.FileContext
+import Hob.Context.StyleContext
+import Hob.Context.UiContext
+import Hob.Ui.CommandEntry
+import Hob.Ui.Editor
+import Hob.Ui.Sidebar
+import Hob.Ui.SidebarSearch
+
+loadGui :: FileContext -> StyleContext -> IO Context
+loadGui fileCtx styleCtx = do
+        _ <- initGUI
+
+        builder <- loadUiBuilder
+        setGtkStyle styleCtx
+
+        ctx <- initCtx builder defaultCommands
+        initMainWindow ctx
+        initEditingArea ctx
+        initSidebar ctx
+        initCommandEntry ctx
+        initActiveModesMonitor ctx
+        return ctx
+    where
+        initCtx builder initCommands = do
+            window <- builderGetObject builder castToWindow "mainWindow"
+            notebook <- builderGetObject builder castToNotebook "tabbedEditArea"
+            cmdEntry <- builderGetObject builder castToEntry "command"
+            treeView <- builderGetObject builder castToTreeView "directoryListing"
+            treeViewSearch <- builderGetObject builder castToEntry "directoryListingSearch"
+            activeModesObject <- builderGetObject builder castToLabel "activeMode"
+            treeModel <- LTS.treeStoreNew []
+            let uiCtx = UiContext window notebook cmdEntry treeView treeViewSearch activeModesObject
+            initContext styleCtx fileCtx uiCtx treeModel initCommands
+
+        loadUiBuilder = do
+            builder <- builderNew
+            builderAddFromFile builder $ uiFile styleCtx
+            return builder
+
+        initSidebar ctx = do
+            let treeView = sidebarTree . uiContext $ ctx
+            let treeViewSearch = sidebarTreeSearch . uiContext $ ctx
+            widgetSetName treeView "directoryListing"
+            widgetSetName treeViewSearch "sidebarSearchEntry"
+            let mainEditNotebook = mainNotebook . uiContext $ ctx
+            newSideBarFileTree ctx treeView $ launchNewFileEditor ctx mainEditNotebook
+            newSideBarFileTreeSearch ctx
+
+        initCommandEntry ctx = do
+            let cmdEntry = commandEntry . uiContext $ ctx
+            widgetSetName cmdEntry "commandEntry"
+            deferredRunner ctx $ newCommandEntry cmdEntry
+
+        initMainWindow ctx = do
+            let window = mainWindow . uiContext $ ctx
+            lastPressedKeyRef <- newIORef ""
+            widgetSetName window "mainWindow"
+            _ <- window `on` keyPressEvent $ do
+                modifier <- eventModifier
+                keyT <- eventKeyName
+                let key = unpack keyT
+                liftIO $ writeIORef lastPressedKeyRef key
+                if key == "Control_L" then return False
+                else invokeKeyCommand ctx (modifier, key)
+            _ <- window `on` keyReleaseEvent $ do
+                modifier <- eventModifier
+                keyT <- eventKeyName
+                lastPressedKey <- liftIO $ readIORef lastPressedKeyRef
+                let key = unpack keyT
+                if (lastPressedKey == key) && (key == "Control_L") then invokeKeyCommand ctx (modifier, key)
+                else return False
+            return ()
+
+        initEditingArea ctx = do
+            let mainEditNotebook = mainNotebook . uiContext $ ctx
+            _ <- mainEditNotebook `on` switchPage $ const $ deferredRunner ctx (emitEvent $ Event "core.editor.focused")
+            return ()
+
+        invokeKeyCommand ctx command = liftIO . runApp ctx $ do
+                                         activeCommands <- getActiveCommands
+                                         maybe (return False)
+                                           (\cmd -> commandExecute cmd >> return True) $
+                                           matchKeyBinding activeCommands command
+
+        initActiveModesMonitor ctx = deferredRunner ctx $ do
+            registerEventHandler (Event "core.editor.focused") refreshModesLabel
+            registerEventHandler (Event "core.mode.change") refreshModesLabel
+
+        refreshModesLabel = do
+            activeCtx <- ask
+            modes <- activeModes
+            let modesUi = activeModesLabel . uiContext $ activeCtx
+            let modesToString = intercalate " | " . filter (not . null) . map modeName
+            liftIO $ labelSetText modesUi $ maybe "-" modesToString modes
+
+        defaultCommands = mconcat $ [
+                                    createMatcherForKeyBinding ([Control], "w") closeCurrentEditorTab,
+                                    createMatcherForKeyBinding ([Control], "s") saveCurrentEditorTab,
+                                    createMatcherForKeyBinding ([Control], "n") editNewFileCommandHandler,
+                                    createMatcherForKeyBinding ([Control], "r") reloadSidebarTreeCommandHandler,
+                                    createMatcherForKeyBinding ([Control], "Tab") focusNextTabCommandHandler,
+                                    createMatcherForKeyBinding ([Shift, Control], "Tab") focusPreviousTabCommandHandler,
+                                    createMatcherForKeyBinding ([Shift, Control], "ISO_Left_Tab") focusPreviousTabCommandHandler,
+                                    createMatcherForKeyBinding ([Control], "t") focusSidebarCommandHandler,
+                                    createMatcherForKeyBinding ([Shift, Control], "T") syncFocusSidebarCommandHandler,
+                                    createMatcherForPrefix "/" searchCommandHandler,
+                                    createMatcherForReplace 's' replaceCommandHandler,
+                                    createMatcherForKeyBinding ([Control], "Control_L") focusCommandEntryCommandHandler,
+                                    createMatcherForKeyBinding ([], "Escape") focusActiveEditorAndExitLastModeCommandHandler
+                                ] ++
+                                [
+                                    createMatcherForKeyBinding ([Control], [intToDigit $ (position + 1) `mod` 10]) $ focusNumberedTabCommandHandler position
+                                        | position <- [0..9]
+                                ]
+
+setGtkStyle :: StyleContext -> IO ()
+setGtkStyle styleCtx = do
+    cssProvider <- cssProviderNew
+    cssProviderLoadFromPath cssProvider $ uiTheme styleCtx
+    maybe (return()) (\screen -> GtkSc.styleContextAddProviderForScreen screen cssProvider 800) =<< screenGetDefault
+
diff --git a/src/lib/Hob/Ui/CommandEntry.hs b/src/lib/Hob/Ui/CommandEntry.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Ui/CommandEntry.hs
@@ -0,0 +1,86 @@
+module Hob.Ui.CommandEntry (newCommandEntry, newCommandEntryDetached) where
+
+import           Control.Monad.Reader
+import           Data.IORef
+import           Data.Text                            (unpack)
+import           Graphics.UI.Gtk
+import qualified Graphics.UI.Gtk.General.StyleContext as GtkSc
+
+import Hob.Context
+import Hob.Control
+
+type PreviewResetState = (PreviewCommandHandler -> App(), App())
+
+commandPreviewResetState :: App PreviewResetState
+commandPreviewResetState = do
+    state <- liftIO $ newIORef Nothing
+    return (
+        liftIO . writeIORef state . Just,
+        do
+            resetCommand <- liftIO $ readIORef state
+            maybeDo previewReset resetCommand
+            liftIO $ writeIORef state Nothing
+        )
+
+newCommandEntry :: Entry -> App ()
+newCommandEntry cmdEntry = do
+    ctx <- ask
+    (onChange, onReturn) <- newCommandEntryDetached cmdEntry
+    let liftedOn a b c = liftIO $ on a b c
+    _ <- cmdEntry `liftedOn` editableChanged $ deferredRunner ctx onChange
+    _ <- cmdEntry `liftedOn` keyPressEvent $ do
+        modifier <- eventModifier
+        key <- eventKeyName
+        case (modifier, unpack key) of
+            ([], "Return") -> liftIO $ deferredRunner ctx onReturn >> return True
+            _ -> return False
+    return ()
+
+newCommandEntryDetached :: Entry -> App (App(), App())
+newCommandEntryDetached cmdEntry = do
+    previewResetState <- commandPreviewResetState
+    return (onChanged previewResetState, onReturn previewResetState)
+    where
+        onChanged =
+            previewCmd cmdEntry
+        onReturn previewResetState = do
+            runCmd cmdEntry previewResetState
+            liftIO $ entrySetText cmdEntry ""
+
+
+previewCmd :: Entry -> PreviewResetState -> App ()
+previewCmd cmdEntry (setLastPreviewCmd, dispatchLastPreviewReset) = do
+    dispatchLastPreviewReset
+    text <- liftIO $ entryGetText cmdEntry
+    if text == "" then
+        setOkStatus cmdEntry
+    else do
+        cmdMatcher <- getActiveCommands
+        let command = matchCommand cmdMatcher text
+        maybe notifyFailure handleCommand command
+    where
+        notifyFailure = setErrorStatus cmdEntry
+        handleCommand command = do
+            setOkStatus cmdEntry
+            maybeDo invokePreview $ commandPreview command
+        invokePreview prev = do
+            previewExecute prev
+            setLastPreviewCmd prev
+
+runCmd :: Entry -> PreviewResetState -> App ()
+runCmd cmdEntry (_, dispatchLastPreviewReset) = do
+    dispatchLastPreviewReset
+    text <- liftIO $ entryGetText cmdEntry
+    cmdMatcher <- getActiveCommands
+    let command = matchCommand cmdMatcher text
+    maybeDo commandExecute command
+
+setErrorStatus :: Entry -> App ()
+setErrorStatus cmdEntry = do
+    cmdEntryStyleContext <- liftIO $ widgetGetStyleContext cmdEntry
+    liftIO $ GtkSc.styleContextAddClass cmdEntryStyleContext "error"
+
+setOkStatus :: Entry -> App ()
+setOkStatus cmdEntry = do
+    cmdEntryStyleContext <- liftIO $ widgetGetStyleContext cmdEntry
+    liftIO $ GtkSc.styleContextRemoveClass cmdEntryStyleContext "error"
diff --git a/src/lib/Hob/Ui/Editor.hs b/src/lib/Hob/Ui/Editor.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Ui/Editor.hs
@@ -0,0 +1,217 @@
+module Hob.Ui.Editor (
+               newEditorForText,
+               getActiveEditorText,
+               getActiveEditorTab,
+               getActiveEditor,
+               invokeOnActiveEditor,
+               getEditorText,
+               getEditorFilePath,
+               setEditorFilePath,
+               getEditorFromNotebookTab,
+               updateEditorTitle
+               ) where
+
+import Control.Monad.Reader
+import Data.Text                  (Text)
+import Filesystem.Path.CurrentOS  (decodeString, encodeString, filename)
+import Graphics.UI.Gtk
+import Graphics.UI.Gtk.SourceView (SourceDrawSpacesFlags (..), SourceView,
+                                   castToSourceView,
+                                   sourceBufferBeginNotUndoableAction,
+                                   sourceBufferEndNotUndoableAction,
+                                   sourceBufferNew,
+                                   sourceBufferSetHighlightSyntax,
+                                   sourceBufferSetLanguage,
+                                   sourceBufferSetStyleScheme,
+                                   sourceViewNewWithBuffer,
+                                   sourceViewSetAutoIndent,
+                                   sourceViewSetDrawSpaces,
+                                   sourceViewSetHighlightCurrentLine,
+                                   sourceViewSetIndentOnTab,
+                                   sourceViewSetIndentWidth,
+                                   sourceViewSetInsertSpacesInsteadOfTabs,
+                                   sourceViewSetShowLineNumbers,
+                                   sourceViewSetTabWidth)
+import System.Glib.GObject        (Quark)
+
+import Hob.Context
+import Hob.Context.FileContext
+import Hob.Context.StyleContext
+import Hob.Context.UiContext
+import Hob.Control
+
+gtkEditor :: SourceView -> Editor
+gtkEditor sourceView = Editor
+            { editorId = const . liftIO $ getEditorId sourceView
+
+            , enterEditorMode = \editor mode -> do
+                modes <- liftIO $ getEditorModes sourceView
+                liftIO $ setEditorModes sourceView $ modes++[mode]
+                return editor
+
+            , exitLastEditorMode = \editor -> do
+                modes <- liftIO $ getEditorModes sourceView
+                if null modes then return editor
+                else do
+                    cleanup $ last modes
+                    liftIO $ setEditorModes sourceView $ init modes
+                    return editor
+
+            , modeStack  = const . liftIO $ getEditorModes sourceView
+
+            , isCurrentlyActive = const $ do
+                ctx <- ask
+                active <- liftIO $ getActiveEditor ctx
+                return $ active == Just sourceView
+            }
+
+newEditorForText :: Notebook -> Maybe FilePath -> Text -> App ()
+newEditorForText targetNotebook filePath text = do
+    ctx <- ask
+    liftIO $ do
+        newId <- idGenerator ctx
+        editor <- createNewEditor ctx
+        setEditorId editor newId
+        setEditorModes editor []
+        updateEditors (editors ctx) $ \oldEditors -> return $ oldEditors ++ [gtkEditor editor]
+    where
+        title = tabTitleForFile filePath
+        setBufferLanguage buffer (Just lang) = sourceBufferSetLanguage buffer (Just lang) >> sourceBufferSetHighlightSyntax buffer True
+        setBufferLanguage _ Nothing = return()
+        createNewEditor ctx = do
+            buffer <- sourceBufferNew Nothing
+            maybeDo (setBufferLanguage buffer <=< sourceLanguage (fileContext ctx)) filePath
+
+            sourceBufferBeginNotUndoableAction buffer
+            textBufferSetText buffer text
+            textBufferSetModified buffer False
+            sourceBufferEndNotUndoableAction buffer
+
+            sourceBufferSetStyleScheme buffer =<< sourceStyleScheme (styleContext ctx) filePath
+
+            editor <- sourceViewNewWithBuffer buffer
+            sourceViewSetShowLineNumbers editor True
+            sourceViewSetAutoIndent editor True
+            sourceViewSetIndentOnTab editor True
+            sourceViewSetIndentWidth editor 4
+            sourceViewSetTabWidth editor 4
+            sourceViewSetInsertSpacesInsteadOfTabs editor True
+            sourceViewSetHighlightCurrentLine editor True
+            sourceViewSetDrawSpaces editor SourceDrawSpacesTrailing
+
+            scrolledWindow <- scrolledWindowNew Nothing Nothing
+            scrolledWindow `containerAdd` editor
+
+            widgetModifyFont editor =<< sourceStyleFont (styleContext ctx) filePath
+
+            widgetShowAll scrolledWindow
+            tabNr <- notebookAppendPage targetNotebook scrolledWindow title
+            notebookSetCurrentPage targetNotebook tabNr
+            notebookSetShowTabs targetNotebook True
+
+            _ <- buffer `on` modifiedChanged $ notebookSetTabLabelText targetNotebook scrolledWindow =<< tabTitleForEditor editor
+
+            setEditorFilePath editor filePath
+            return editor
+
+
+getActiveEditorText :: Context -> IO (Maybe Text)
+getActiveEditorText ctx = do
+    editor <- getActiveEditor ctx
+    maybe (return Nothing) ((return . Just) <=< getEditorText) editor
+
+getEditorText :: TextViewClass a => a -> IO Text
+getEditorText textEdit = do
+    textBuf <- textViewGetBuffer textEdit
+    get textBuf textBufferText
+
+getActiveEditor :: Context -> IO (Maybe SourceView)
+getActiveEditor = maybe (return Nothing) getEditorFromNotebookTab <=< getActiveEditorTab
+
+invokeOnActiveEditor :: (SourceView -> IO()) -> App ()
+invokeOnActiveEditor actions = do
+    ctx <- ask
+    editor <- liftIO $ getActiveEditor ctx
+    liftIO $ maybeDo actions editor
+
+getEditorFromNotebookTab :: Widget -> IO (Maybe SourceView)
+getEditorFromNotebookTab currentlyActiveEditor =
+    if currentlyActiveEditor `isA` gTypeScrolledWindow then do
+        let textEditScroller = castToScrolledWindow currentlyActiveEditor
+        textEdit <- binGetChild textEditScroller
+        return $ fmap castToSourceView textEdit
+    else return Nothing
+
+getActiveEditorTab :: Context -> IO (Maybe Widget)
+getActiveEditorTab ctx = do
+    pageNum <- notebookGetCurrentPage tabbed
+    if pageNum < 0 then
+        return Nothing
+    else do
+        tabs <- containerGetChildren tabbed
+        return $ Just $ tabs!!pageNum
+    where tabbed = mainNotebook.uiContext $ ctx
+
+tabTitleForFile :: Maybe FilePath -> String
+tabTitleForFile (Just filePath) = filename' filePath
+    where filename' = encodeString . filename . decodeString
+tabTitleForFile Nothing = "(new file)"
+
+tabTitleForEditor :: SourceView -> IO String
+tabTitleForEditor editor = do
+    filePath <- getEditorFilePath editor
+    buffer <- textViewGetBuffer editor
+    modified <- buffer `get` textBufferModified
+    return $ if modified then tabTitleForFile filePath ++ "*" else tabTitleForFile filePath
+
+setEditorFilePath :: SourceView -> Maybe FilePath -> IO ()
+setEditorFilePath editor filePath = do
+    quark <- fileNameQuark
+    objectSetAttribute quark editor filePath
+
+getEditorFilePath :: SourceView -> IO (Maybe FilePath)
+getEditorFilePath editor = do
+    quark <- fileNameQuark
+    objectGetAttributeUnsafe quark editor
+
+setEditorId :: SourceView -> Int -> IO ()
+setEditorId editor identifier = do
+    quark <- editorIdQuark
+    objectSetAttribute quark editor $ Just identifier
+
+getEditorId :: SourceView -> IO Int
+getEditorId editor = do
+    quark <- editorIdQuark
+    mRet <- objectGetAttributeUnsafe quark editor
+    case mRet of
+        Just ret -> return ret
+        Nothing -> error "editor has no id assigned"
+
+setEditorModes :: SourceView -> [Mode] -> IO ()
+setEditorModes editor modes = do
+    quark <- editorModesQuark
+    objectSetAttribute quark editor $ Just modes
+
+getEditorModes :: SourceView -> IO [Mode]
+getEditorModes editor = do
+    quark <- editorModesQuark
+    mRet <- objectGetAttributeUnsafe quark editor
+    case mRet of
+        Just ret -> return ret
+        Nothing -> error "editor has no modes assigned"
+
+updateEditorTitle :: SourceView -> IO ()
+updateEditorTitle editor = do
+    Just scrolledW <- widgetGetParent editor
+    Just notebookW <- widgetGetParent scrolledW
+    let notebook = castToNotebook notebookW
+    notebookSetTabLabelText notebook scrolledW =<< tabTitleForEditor editor
+
+fileNameQuark :: IO Quark
+fileNameQuark = quarkFromString "fileName"
+
+editorIdQuark :: IO Quark
+editorIdQuark = quarkFromString "editorId"
+
+editorModesQuark :: IO Quark
+editorModesQuark = quarkFromString "editorModes"
diff --git a/src/lib/Hob/Ui/Editor/Search.hs b/src/lib/Hob/Ui/Editor/Search.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Ui/Editor/Search.hs
@@ -0,0 +1,153 @@
+module Hob.Ui.Editor.Search (
+        highlightSearchPreview,
+        resetSearchPreview,
+        findFirstFromCursor,
+        findNext,
+        findPrevious,
+        resetSearch,
+        getEditorSearchString,
+        startReplace,
+        replaceNext,
+        resetReplace,
+    ) where
+
+import Control.Monad              (when)
+import Data.Text                  (pack)
+import Graphics.UI.Gtk
+import Graphics.UI.Gtk.SourceView (SourceView)
+import System.Glib.GObject        (Quark)
+
+import Hob.Control
+
+highlightSearchPreview :: SourceView -> String -> IO()
+highlightSearchPreview editor text = do
+    buffer <- textViewGetBuffer editor
+    tagTable <- textBufferGetTagTable buffer
+    tag <- maybe (addNewSearchTag tagTable) return =<< textTagTableLookup tagTable "search"
+    (start, end) <- textBufferGetBounds buffer
+    addNewSearchTags buffer tag start end
+    where
+        addNewSearchTag tagTable = do
+            tag <- textTagNew $ Just $ pack "search"
+            tag `set` [textTagBackground := "#707550"]
+            textTagTableAdd tagTable tag
+            return tag
+        addNewSearchTags buffer tag start end = do
+            result <- textIterForwardSearch start text [TextSearchTextOnly] (Just end)
+            case result of
+                Just (matchStart, matchEnd) -> do
+                    textBufferApplyTag buffer tag matchStart matchEnd
+                    addNewSearchTags buffer tag matchEnd end
+                Nothing -> return()
+
+resetSearch :: SourceView -> IO()
+resetSearch editor = do
+    setEditorSearchString editor Nothing
+    resetSearchPreview editor
+
+resetSearchPreview :: SourceView -> IO()
+resetSearchPreview editor = do
+    buffer <- textViewGetBuffer editor
+    tagTable <- textBufferGetTagTable buffer
+    maybeDo (removeEditorTag buffer) =<< textTagTableLookup tagTable "search"
+    where
+        removeEditorTag buffer tag = do
+            (start, end) <- textBufferGetBounds buffer
+            textBufferRemoveTag buffer tag start end
+
+findNext :: SourceView -> IO()
+findNext editor = do
+     searchString <- getEditorSearchString editor
+     maybeDo (searchExecute editor) searchString
+
+findPrevious :: SourceView -> IO()
+findPrevious editor = do
+    searchString <- getEditorSearchString editor
+    maybeDo (searchExecuteBackwards editor) searchString
+
+findFirstFromCursor :: SourceView -> String -> IO()
+findFirstFromCursor editor text = do
+    setEditorSearchString editor $ Just text
+    highlightSearchPreview editor text
+    searchExecute editor text
+
+
+startReplace :: SourceView -> String -> String -> IO()
+startReplace editor textFrom textTo = do
+    findFirstFromCursor editor textFrom
+    setEditorReplaceString editor $ Just textTo
+
+replaceNext :: SourceView -> IO()
+replaceNext editor = do
+    maybeDo replaceSelectionWith =<< getEditorReplaceString editor
+    findNext editor
+    where
+        replaceSelectionWith replaceText = maybeDo (replaceSearchSelectionWith replaceText) =<< getEditorSearchString editor
+        replaceSearchSelectionWith replaceText searchText = do
+            buffer <- textViewGetBuffer editor
+            (s, e) <- textBufferGetSelectionBounds buffer
+            selectedText <- textBufferGetText buffer s e False
+            when (searchText == selectedText) $ do
+                textBufferDelete buffer s e
+                textBufferInsert buffer s replaceText
+
+resetReplace :: SourceView -> IO()
+resetReplace editor = do
+    resetSearch editor
+    setEditorReplaceString editor Nothing
+
+
+searchExecute :: SourceView -> String -> IO()
+searchExecute editor text = do
+    buffer <- textViewGetBuffer editor
+    (_, start) <- textBufferGetSelectionBounds buffer
+    maybe (retryFromStart buffer) (selectMatch editor buffer) =<< findNextResult start
+    where
+        findNextResult start = textIterForwardSearch start text [TextSearchTextOnly] Nothing
+        retryFromStart buffer = do
+            (start, _) <- textBufferGetBounds buffer
+            maybeDo (selectMatch editor buffer) =<< findNextResult start
+
+searchExecuteBackwards :: SourceView -> String -> IO()
+searchExecuteBackwards editor text = do
+    buffer <- textViewGetBuffer editor
+    (end, _) <- textBufferGetSelectionBounds buffer
+    maybe (retryFromEnd buffer) (selectMatch editor buffer) =<< findPreviousResult end
+    where
+        findPreviousResult end = textIterBackwardSearch end text [TextSearchTextOnly] Nothing
+        retryFromEnd buffer = do
+            (_, end) <- textBufferGetBounds buffer
+            maybeDo (selectMatch editor buffer) =<< findPreviousResult end
+
+selectMatch :: (TextViewClass tv, TextBufferClass tb) => tv -> tb -> (TextIter, TextIter) -> IO ()
+selectMatch editor buffer (start, end) = do
+    textBufferSelectRange buffer start end
+    caretMark <- textBufferGetInsert buffer
+    textViewScrollToMark editor caretMark 0.1 Nothing
+
+setEditorSearchString :: SourceView -> Maybe String -> IO ()
+setEditorSearchString editor searchString = do
+    quark <- searchStringQuark
+    objectSetAttribute quark editor searchString
+
+getEditorSearchString :: SourceView -> IO (Maybe String)
+getEditorSearchString editor = do
+    quark <- searchStringQuark
+    objectGetAttributeUnsafe quark editor
+
+searchStringQuark :: IO Quark
+searchStringQuark = quarkFromString "activeSearchString"
+
+setEditorReplaceString :: SourceView -> Maybe String -> IO ()
+setEditorReplaceString editor replaceString = do
+    quark <- replaceStringQuark
+    objectSetAttribute quark editor replaceString
+
+getEditorReplaceString :: SourceView -> IO (Maybe String)
+getEditorReplaceString editor = do
+    quark <- replaceStringQuark
+    objectGetAttributeUnsafe quark editor
+
+replaceStringQuark :: IO Quark
+replaceStringQuark = quarkFromString "activeReplaceString"
+
diff --git a/src/lib/Hob/Ui/Sidebar.hs b/src/lib/Hob/Ui/Sidebar.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Ui/Sidebar.hs
@@ -0,0 +1,108 @@
+module Hob.Ui.Sidebar (
+    newSideBarFileTree,
+    reloadSidebarTree,
+    activateSidebarPath,
+    syncPathToSidebar,
+    nameColumn,
+    pathColumn
+    ) where
+
+import Control.Monad.Reader
+import Data.List                 (isPrefixOf)
+import Graphics.UI.Gtk
+import Graphics.UI.Gtk.ModelView as Mv
+import GtkExtras.LargeTreeStore  as LTS
+
+import Hob.Command.NewTab
+import Hob.Context
+import Hob.Context.FileContext
+import Hob.Context.UiContext
+import Hob.Control
+import Hob.DirectoryTree
+
+newSideBarFileTree :: Context -> TreeView -> NewFileEditorLauncher -> IO ()
+newSideBarFileTree ctx treeView launchFile = do
+    let treeStore = fileTreeStore ctx
+    runApp ctx reloadSidebarTree
+    initNameColumn treeStore
+
+    treeViewSetHeadersVisible treeView False
+    treeViewSetEnableSearch treeView False
+    treeViewSetModel treeView treeStore
+
+    _ <- treeView `on` rowCollapsed $ \ _ _ -> treeViewColumnsAutosize treeView
+    _ <- treeView `on` rowActivated $ \ path _ -> activateRow path =<< LTS.treeStoreGetValue treeStore path
+
+    return ()
+
+    where
+        initNameColumn treeStore = do
+            customStoreSetColumn treeStore nameColumn elementLabel
+            customStoreSetColumn treeStore pathColumn elementPath
+
+            col <- treeViewColumnNew
+
+            rend <- Mv.cellRendererTextNew
+            Mv.cellLayoutPackStart col rend True
+            Mv.cellLayoutSetAttributes col rend treeStore (\v -> [Mv.cellText := elementLabel v])
+
+            _ <- treeViewAppendColumn treeView col
+            return ()
+
+        activateRow :: TreePath -> DirectoryTreeElement -> IO ()
+        activateRow path el = if isDirectory el then void $ treeViewExpandRow treeView path False
+                              else launchFile . elementPath $ el
+
+
+nameColumn :: ColumnId row String
+nameColumn = makeColumnIdString 0
+
+pathColumn :: ColumnId row FilePath
+pathColumn = makeColumnIdString 1
+
+reloadSidebarTree :: App ()
+reloadSidebarTree = do
+    ctx <- ask
+    let treeStore = fileTreeStore ctx
+    let fileCtx = fileContext ctx
+    let fileTreeLoader = contextFileTreeLoader fileCtx
+    liftIO $ LTS.treeStoreClear treeStore
+    liftIO $ LTS.treeStoreInsertForest treeStore [] 0 =<< fileTreeLoader
+    emitEvent $ Event "core.sidebar.reload"
+
+
+activateSidebarPath :: TreeViewClass tv => tv -> TreePath -> IO ()
+activateSidebarPath treeView path = do
+    treeViewCollapseAll treeView
+    treeViewExpandToPath treeView path
+    treeViewSetCursor treeView path Nothing
+
+
+
+
+syncPathToSidebar :: FilePath -> App()
+syncPathToSidebar filePath = do
+    ctx <- ask
+    let treeView = sidebarTree.uiContext $ ctx
+    liftIO (maybeDo (syncTreeViewModel treeView) =<< treeViewGetModel treeView)
+    where
+        syncTreeViewModel treeView model = do
+            mStartIter <- treeModelGetIterFirst model
+            maybeDo (syncToMatchingPath treeView model) mStartIter
+        syncToMatchingPath treeView model startingIter =
+            maybeDo (syncToIter treeView model) =<< findFilePath model filePath startingIter
+        syncToIter treeView model iter = activateSidebarPath treeView =<< treeModelGetPath model iter
+
+findFilePath :: TreeModelClass self =>
+                self -> FilePath -> TreeIter -> IO (Maybe TreeIter)
+findFilePath model filePath iter = do
+        path <- treeModelGetValue model iter pathColumn
+        if path == filePath then return $ Just iter
+        else if (path++"/") `isPrefixOf` filePath then matchChildren
+        else matchNextSibling
+    where matchChildren = recurseToMaybeIter =<< treeModelIterChildren model iter
+          matchNextSibling = recurseToMaybeIter =<< treeModelIterNext model iter
+          recurseToMaybeIter Nothing = return Nothing
+          recurseToMaybeIter (Just it) = findFilePath model filePath it
+
+
diff --git a/src/lib/Hob/Ui/SidebarSearch.hs b/src/lib/Hob/Ui/SidebarSearch.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Hob/Ui/SidebarSearch.hs
@@ -0,0 +1,349 @@
+module Hob.Ui.SidebarSearch (
+        newSideBarFileTreeSearch,
+        startSidebarSearch,
+        updateSidebarSearch,
+        continueSidebarSearch,
+        continueSidebarSearchBackwards,
+        initFileTreeIndex,
+    ) where
+
+import           Control.Concurrent.MVar              (newMVar, readMVar,
+                                                       swapMVar)
+import           Control.Monad.Reader
+import           Data.Char                            (isPrint, toLower)
+import           Data.List                            (isPrefixOf, sortBy)
+import           Data.Maybe
+import           Data.Text                            (unpack)
+import           Data.Tree
+import qualified Data.Vector                          as V
+import           Graphics.UI.Gtk
+import           Graphics.UI.Gtk.General.StyleContext (styleContextAddClass,
+                                                       styleContextRemoveClass)
+
+import Hob.Context
+import Hob.Context.FileContext
+import Hob.Context.UiContext
+import Hob.Control
+import Hob.DirectoryTree
+import Hob.Ui.Sidebar
+
+{-| Index based on suffix arrays
+
+The two vectors contain the path element surrounded by slashes '/'
+and a sorted symbol positions respectively. The index is generated
+by a function 'mkIndex'.
+
+Example:
+
+@
+mkIndex "example"
+LetterIndex (fromList "/example/") (fromList [0,8,3,1,7,6,4,5,2])
+@
+-}
+data LetterIndex = LetterIndex (V.Vector Char) (V.Vector Int) deriving Show
+data SearchIndexNode a = PathNode LetterIndex a (SearchIndex a) | LeafNode LetterIndex a deriving Show
+type SearchIndex a = [SearchIndexNode a]
+
+instance Eq a => Eq (SearchIndexNode a) where
+    (LeafNode _ a) == (LeafNode _ b) = a == b
+    (PathNode _ a _) == (PathNode _ b _) = a == b
+    _ == _ = False
+
+type DirectorySearchIndex = SearchIndex DirectoryTreeElement
+
+mkIndex :: String -> LetterIndex
+mkIndex s = LetterIndex word indices
+     where
+        word = V.fromList . map toLower $ '/':s++"/"
+        indices = V.fromList . sortBy indexOrdering . take (V.length word) $ [0..]
+        indexOrdering i1 i2 = let c1 = word V.! i1
+                                  c2 = word V.! i2
+                                  cmp = compare c1 c2
+                              in if cmp == EQ then compare i1 i2 else cmp
+
+{-| Find all positions for a character -}
+findOccurrences :: LetterIndex -> Char -> [Int]
+findOccurrences (LetterIndex word idx) c = range mid
+    where
+        binSearch low high
+          | low > high = Nothing
+          | value > c = binSearch low (middle-1)
+          | value < c = binSearch (middle+1) high
+          | otherwise = Just middle
+            where
+                value = word V.! (idx V.! middle)
+                middle = (low + high) `quot` 2
+
+        mid = binSearch 0 (highBound-1)
+
+        startFrom x
+          | x < 0 = 0
+          | otherwise = if (word V.! (idx V.! x)) == c then startFrom (x-1) else x+1
+
+        endFrom x
+          | x == highBound = highBound - 1
+          | otherwise = if (word V.! (idx V.! x)) == c then endFrom (x+1) else x-1
+
+        range Nothing = []
+        range (Just x) = V.toList . V.slice s (e-s+1) $ idx
+            where
+                e = endFrom x
+                s = startFrom x
+        highBound = V.length word
+
+{-| Match query against the index
+
+A query is a list for strings.
+Each string is matched as a substring of the given index.
+Then the next query string is matched.
+Once there is a match failure, the rest of the query is returned as the result.
+
+Example:
+
+@
+matchQuery (mkIndex "example") ["/ex", "ple"] = []
+matchQuery (mkIndex "example") ["/ex", "pla"] = ["pla"]
+matchQuery (mkIndex "example") ["/ex", "pla", "the rest"] = ["pla", "the rest"]
+@
+-}
+matchQuery :: LetterIndex -> [String] -> [String]
+matchQuery index@(LetterIndex word _) = match 0
+    where
+          match _ [] = []
+          match from ([]:qs) = match from qs
+          match from (q:qs)
+            | from > highBound = q:qs
+            | otherwise = if null subMatches then q:qs
+                          else let from' = head subMatches
+                               in match from' qs
+
+                where
+                    subMatches = mapMaybe (`matchSubString` q) $ findPossibleStarts from q
+
+          findPossibleStarts _ [] = []
+          findPossibleStarts from (c:_) = filter (>=from) . findOccurrences index $ c
+
+          matchSubString from [] = Just from
+          matchSubString from (c:cs)
+            | from > highBound = Nothing
+            | (word V.! from) == c = matchSubString (from+1) cs
+            | otherwise = Nothing
+
+          highBound = V.length word - 1
+
+buildIndex :: Forest DirectoryTreeElement -> DirectorySearchIndex
+buildIndex = fmap addNode
+    where
+        addNode Node{rootLabel=element, subForest=children} = if isDirectory element then pathNode else leafNode
+            where
+                pathNode = PathNode index element (buildIndex children)
+                leafNode = LeafNode index element
+                index = mkIndex $ elementLabel element
+
+findNextMatch :: DirectorySearchIndex -> FilePath -> [String] -> Maybe DirectoryTreeElement
+findNextMatch index previous = findMatch $ filterFromPath index previous
+
+findPreviousMatch :: DirectorySearchIndex -> FilePath -> [String] -> Maybe DirectoryTreeElement
+findPreviousMatch index next = findMatch $ filterFromPath (reverseIndex index) next
+    where
+        reverseIndex = map reverseNode . reverse
+            where
+                reverseNode n@(LeafNode _ _) = n
+                reverseNode (PathNode letterIdx el children) = PathNode letterIdx el $ reverseIndex children
+
+filterFromPath :: DirectorySearchIndex -> FilePath -> DirectorySearchIndex
+filterFromPath [] _ = []
+filterFromPath (node@(LeafNode _ el):is) path
+ | elementPath el == path = node:is
+ | otherwise = filterFromPath is path
+filterFromPath (node@(PathNode lidx el children):is) path
+ | elementPath el == path = node:is
+ | (elementPath el ++ "/") `isPrefixOf` path = PathNode lidx el (filterFromPath children path) : is
+ | otherwise = filterFromPath is path
+
+
+findMatch :: DirectorySearchIndex -> [String] -> Maybe DirectoryTreeElement
+findMatch [] _ = Nothing
+findMatch (LeafNode idx el : is) queries
+ | matchSuccess = Just el
+ | otherwise = findMatch is queries
+    where matchSuccess = null $ matchQuery idx queries
+findMatch (PathNode idx _ children : is) queries = mplus childrenMatch $ findMatch is queries
+    where childrenMatch = findMatch children $ matchQuery idx queries
+
+{-| Prepare the query string
+
+Parse the query into a query for matching.
+
+Example:
+@
+prepareQueries "example" = ["example"]
+prepareQueries "exam ple" = ["exam","ple"]
+prepareQueries "exam/ple" = ["exam/","/ple"]
+prepareQueries "exam//ple" = ["exam/","/","/ple"]
+prepareQueries "exam/ ple" = ["exam/","ple"]
+@
+-}
+prepareQueries :: String -> [String]
+prepareQueries = concatMap breakSlashes . breakSpaces . map toLower
+    where
+        breakSpaces s = case break (== ' ') s of
+                            ([], []) -> []
+                            (a, []) -> [a]
+                            ([], ' ':b) -> breakSpaces b
+                            (a, ' ':b) -> a : breakSpaces b
+                            (a, b) -> a : breakSpaces b
+        breakSlashes s = case break (== '/') s of
+                            ([], []) -> []
+                            (a, []) -> [a]
+                            ([], '/':b) -> prependToFirst '/' $ breakSlashes b
+                            (a, '/':b) -> (a++"/") : prependToFirst '/' (breakSlashes b)
+                            (a, b) -> (a++"/") : prependToFirst '/' (breakSlashes b)
+        prependToFirst _ [] = []
+        prependToFirst a ([]:xs) = [a]:xs
+        prependToFirst a (x@(q:_):xs)
+         | a == q = [a]:x:xs
+         | otherwise = (a:x):xs
+
+newSideBarFileTreeSearch :: Context -> IO ()
+newSideBarFileTreeSearch ctx = do
+    let treeView = sidebarTree.uiContext $ ctx
+    let searchEntry = sidebarTreeSearch.uiContext $ ctx
+    index <- newMVar =<< runApp ctx initFileTreeIndex
+    runApp ctx $ registerEventHandler (Event "core.sidebar.reload") (initFileTreeIndex >>= (liftIO . swapMVar index) >> return())
+    _ <- treeView `on` keyPressEvent $ do
+        key <- eventKeyVal
+        maybe (return False) startSearch $ keyToChar key
+    _ <- searchEntry `on` editableChanged $ runApp ctx $ liftIO (readMVar index) >>= updateSidebarSearch
+    _ <- searchEntry `on` focusOutEvent $ liftIO $ widgetHide searchEntry >> return False
+    _ <- searchEntry `on` keyPressEvent $ do
+        modifier <- eventModifier
+        if Prelude.null modifier then do
+            key <- eventKeyName
+            case unpack key of
+                "Down" -> liftIO $ runApp ctx $ liftIO (readMVar index) >>= continueSidebarSearch >> return True
+                "Up" -> liftIO $ runApp ctx $ liftIO (readMVar index) >>= continueSidebarSearchBackwards >> return True
+                "Return" -> stopSearchAndActivateResult treeView searchEntry
+                _ -> return False
+        else return False
+    return ()
+    where
+        startSearch firstChar
+            | isPrint firstChar = liftIO $ do
+                startSidebarSearch ctx [firstChar]
+                return True
+            | otherwise = return False
+        stopSearchAndActivateResult treeView searchEntry = liftIO $ do
+            widgetGrabFocus treeView
+            widgetHide searchEntry
+            (path, _) <- treeViewGetCursor treeView
+            column <- treeViewGetColumn treeView 0
+            maybeDo (treeViewRowActivated treeView path) column
+            return True
+
+initFileTreeIndex :: App DirectorySearchIndex
+initFileTreeIndex = do
+    fileCtx <- fromContext fileContext
+    nodeForest <- liftIO $ contextFileTreeLoader fileCtx
+    return $ buildIndex nodeForest
+
+startSidebarSearch :: Context -> String -> IO ()
+startSidebarSearch ctx searchString = do
+    let entry = sidebarTreeSearch.uiContext $ ctx
+        sidebar = sidebarTree . uiContext $ ctx
+        len = length searchString
+    treeViewSetCursor sidebar [] Nothing
+    entrySetText entry ""
+    entrySetText entry searchString
+    widgetShow entry
+    widgetGrabFocus entry
+    editableSelectRegion entry len len
+
+updateSidebarSearch :: DirectorySearchIndex -> App ()
+updateSidebarSearch index = invokeOnTreeViewAndModel $ continueSearch index selectNextMatch iterOnSelection
+    where
+        iterOnSelection treeView model = do
+            (path, _) <- liftIO $ treeViewGetCursor treeView
+            selectedIter <- liftIO $ treeModelGetIter model path
+            maybe (treeModelGetIterFirst model) (return.Just) selectedIter
+
+continueSidebarSearch :: DirectorySearchIndex -> App ()
+continueSidebarSearch index = invokeOnTreeViewAndModel $ continueSearch index selectNextMatch iterAfterSelection
+    where
+        iterAfterSelection treeView model = do
+            (path, _) <- treeViewGetCursor treeView
+            currentIter <- treeModelGetIter model path
+            maybe (return Nothing) (findNextSubtree model) currentIter
+
+continueSidebarSearchBackwards :: DirectorySearchIndex -> App ()
+continueSidebarSearchBackwards index = invokeOnTreeViewAndModel $ continueSearch index selectPreviousMatch iterBeforeSelection
+    where
+        iterBeforeSelection treeView model = do
+            (path, _) <- treeViewGetCursor treeView
+            currentIter <- treeModelGetIter model path
+            maybe (return Nothing) (findPreviousSubtree model) currentIter
+
+
+continueSearch :: t2 -> (t2 -> t3 -> Entry -> String -> a -> App()) -> (t1 -> t3 -> IO (Maybe a)) -> t1 -> t3 -> App()
+continueSearch index selectMatch selectionIter treeView model = do
+    ctx <- ask
+    let searchEntry = sidebarTreeSearch.uiContext $ ctx
+    searchString <- liftIO $ entryGetText searchEntry
+    maybeFirstIter <- liftIO $ selectionIter treeView model
+    maybeDo (selectMatch index model searchEntry searchString) maybeFirstIter
+
+invokeOnTreeViewAndModel :: (TreeView -> TreeModel -> App ()) -> App ()
+invokeOnTreeViewAndModel fnc = do
+    ctx <- ask
+    let treeView = sidebarTree.uiContext $ ctx
+    model <- liftIO $ treeViewGetModel treeView
+    maybeDo (fnc treeView) model
+
+selectNextMatch :: (TreeModelClass tm, EntryClass e) => DirectorySearchIndex -> tm -> e -> String -> TreeIter -> App ()
+selectNextMatch index treeModel searchEntry searchString currentIter = do
+    path <- liftIO $ treeModelGetValue treeModel currentIter pathColumn
+    case findNextMatch index path (prepareQueries searchString) of
+        Just match -> do
+            liftIO $ unsetErrorState searchEntry
+            syncPathToSidebar $ elementPath match
+        Nothing -> liftIO $ setErrorState searchEntry
+
+selectPreviousMatch :: (TreeModelClass tm, EntryClass e) => DirectorySearchIndex -> tm -> e -> String -> TreeIter -> App ()
+selectPreviousMatch index treeModel searchEntry searchString currentIter = do
+    path <- liftIO $ treeModelGetValue treeModel currentIter pathColumn
+    case findPreviousMatch index path (prepareQueries searchString) of
+        Just match -> do
+            liftIO $ unsetErrorState searchEntry
+            syncPathToSidebar $ elementPath match
+        Nothing -> liftIO $ setErrorState searchEntry
+
+setErrorState :: EntryClass e => e -> IO ()
+setErrorState searchEntry = do
+    widgetStyleContext <- widgetGetStyleContext searchEntry
+    styleContextAddClass widgetStyleContext "error"
+
+unsetErrorState :: EntryClass e => e -> IO ()
+unsetErrorState searchEntry = do
+    widgetStyleContext <- widgetGetStyleContext searchEntry
+    styleContextRemoveClass widgetStyleContext "error"
+
+findNextSubtree :: TreeModelClass treeModel => treeModel -> TreeIter -> IO (Maybe TreeIter)
+findNextSubtree model iter = do
+    next <- treeModelIterNext model iter
+    if isJust next then return next
+    else maybe (return Nothing) (findNextSubtree model) =<< treeModelIterParent model iter
+
+findPreviousSubtree :: TreeModelClass treeModel => treeModel -> TreeIter -> IO (Maybe TreeIter)
+findPreviousSubtree model iter = do
+    prev <- treeModelIterPrevious model iter
+    if isJust prev then return prev
+    else maybe (return Nothing) (findPreviousSubtree model) =<< treeModelIterParent model iter
+
+treeModelIterPrevious :: TreeModelClass treeModel => treeModel -> TreeIter -> IO (Maybe TreeIter)
+treeModelIterPrevious model iter = do
+    parent <- treeModelIterParent model iter
+    currentPath <- treeModelGetPath model iter
+    let nth = last currentPath
+    if nth > 0 then treeModelIterNthChild model parent (nth-1)
+    else return Nothing
+
diff --git a/test/Hob/Command/CloseCurrentTabSpec.hs b/test/Hob/Command/CloseCurrentTabSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Command/CloseCurrentTabSpec.hs
@@ -0,0 +1,35 @@
+module Hob.Command.CloseCurrentTabSpec (main, spec) where
+
+import Test.Hspec
+
+import Graphics.UI.Gtk
+
+import           Hob.Command.CloseCurrentTab
+import           Hob.Command.NewTab          (launchNewFileEditor)
+import           Hob.Context
+import qualified Hob.Context.UiContext       as HC
+
+import HobTest.Context.Stubbed
+import HobTest.Control
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+    describe "close current tab command" $
+        it "closes the currently active editor tab" $ do
+            ctx <- loadStubbedContext
+            launchEditorTab ctx "/xxx/testName.hs"
+            runCtxActions ctx $ commandExecute closeCurrentEditorTab
+            pagesAfterActivatingDirectory <- getNumberOfEditorPages ctx
+            pagesAfterActivatingDirectory `shouldBe` 0
+
+launchEditorTab :: Context -> String -> IO ()
+launchEditorTab ctx file = do
+    let notebook = HC.mainNotebook . uiContext $ ctx
+    launchNewFileEditor ctx notebook file
+
+getNumberOfEditorPages :: Context -> IO Int
+getNumberOfEditorPages = notebookGetNPages . HC.mainNotebook . uiContext
+
diff --git a/test/Hob/Command/FindTextSpec.hs b/test/Hob/Command/FindTextSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Command/FindTextSpec.hs
@@ -0,0 +1,70 @@
+module Hob.Command.FindTextSpec (main, spec) where
+
+import Control.Monad.State (liftIO)
+import Data.IORef
+import Data.Maybe
+import Data.Text           (pack)
+import Graphics.UI.Gtk
+import Test.Hspec
+
+import           Hob.Command.FindText
+import           Hob.Context
+import qualified Hob.Context.UiContext as HC
+import           Hob.Ui                (getActiveEditor)
+import           Hob.Ui.Editor         (newEditorForText)
+
+import HobTest.Context.Default
+import HobTest.Control
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+  describe "search command handler" $ do
+    it "focuses editor on execute" $ do
+      (ctx, _) <- loadGuiAndExecuteSearch
+      editorFocused <- widgetGetIsFocus . fromJust =<< getActiveEditor ctx
+      editorFocused `shouldBe` True
+
+    it "enters the search mode" $ do
+      (ctx, _) <- loadGuiAndExecuteSearch
+      ref <- newIORef Nothing
+      runCtxActions ctx $ activeModes >>= (liftIO . writeIORef ref)
+      modes <- readIORef ref
+      (modeName . last . fromJust) modes `shouldBe` "search"
+
+  describe "search reset command on mode exit" $
+    it "stops the next search" $ do
+      (ctx, buffer) <- loadGuiAndExecuteSearch
+      runCtxActions ctx exitLastMode
+      runCtxActions ctx $ commandExecute searchNextCommandHandler
+      (start, end) <- getSelectionOffsets buffer
+      (start, end) `shouldBe` (0, 4)
+
+loadGuiAndPreviewSearch :: IO (Context, TextBuffer)
+loadGuiAndPreviewSearch = do
+    ctx <- loadDefaultContext
+    let notebook = HC.mainNotebook . uiContext $ ctx
+    runCtxActions ctx $ newEditorForText notebook Nothing $ pack "text - initial text! text"
+    mEditor <- getActiveEditor ctx
+    let editor = fromJust mEditor
+    runCtxActions ctx $ (previewExecute . fromJust . commandPreview) (searchCommandHandler "text")
+    buffer <- textViewGetBuffer editor
+    return (ctx, buffer)
+
+loadGuiAndExecuteSearch :: IO (Context, TextBuffer)
+loadGuiAndExecuteSearch = do
+    (ctx, buffer) <- loadGuiAndPreviewSearch
+    runCtxActions ctx $ (previewReset . fromJust . commandPreview) (searchCommandHandler "text")
+    iterBufferStart <- textBufferGetIterAtOffset buffer 0
+    textBufferSelectRange buffer iterBufferStart iterBufferStart
+    runCtxActions ctx $ commandExecute (searchCommandHandler "text")
+    return (ctx, buffer)
+
+getSelectionOffsets :: TextBuffer -> IO (Int, Int)
+getSelectionOffsets buffer = do
+    (iterStart, iterEnd) <- textBufferGetSelectionBounds buffer
+    start <- textIterGetOffset iterStart
+    end <- textIterGetOffset iterEnd
+    return (start, end)
diff --git a/test/Hob/Command/FocusCommandEntrySpec.hs b/test/Hob/Command/FocusCommandEntrySpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Command/FocusCommandEntrySpec.hs
@@ -0,0 +1,51 @@
+module Hob.Command.FocusCommandEntrySpec (main, spec) where
+
+import Data.Maybe
+import Graphics.UI.Gtk
+
+import           Hob.Command.FocusCommandEntry
+import           Hob.Command.NewTab
+import           Hob.Context
+import qualified Hob.Context.UiContext         as HC
+import           Hob.Ui
+
+import Test.Hspec
+
+import HobTest.Context.Default
+import HobTest.Control
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "focus command entry command" $ do
+    it "focuses the command entry" $ do
+      ctx <- loadDefaultContext
+      focused <- toggleFocusOnCommandEntryAndReturnState ctx
+      focused `shouldBe` True
+
+    it "focus stays on toggle if there is no editor to focus to" $ do
+      ctx <- loadDefaultContext
+      runCtxActions ctx $ commandExecute toggleFocusOnCommandEntryCommandHandler
+      focused <- toggleFocusOnCommandEntryAndReturnState ctx
+      focused `shouldBe` True
+
+    it "focus moves to editor on toggle" $ do
+      ctx <- launchNewFile
+      runCtxActions ctx $ commandExecute toggleFocusOnCommandEntryCommandHandler
+      commandFocused <- toggleFocusOnCommandEntryAndReturnState ctx
+      editorFocused <- widgetGetIsFocus . fromJust =<< getActiveEditor ctx
+      commandFocused `shouldBe` False
+      editorFocused `shouldBe` True
+
+launchNewFile :: IO Context
+launchNewFile = do
+    ctx <- loadDefaultContext
+    runCtxActions ctx editNewFile
+    return ctx
+
+toggleFocusOnCommandEntryAndReturnState :: Context -> IO Bool
+toggleFocusOnCommandEntryAndReturnState ctx = do
+    runCtxActions ctx $ commandExecute toggleFocusOnCommandEntryCommandHandler
+    widgetGetIsFocus $ HC.commandEntry . uiContext $ ctx
diff --git a/test/Hob/Command/FocusNextTabSpec.hs b/test/Hob/Command/FocusNextTabSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Command/FocusNextTabSpec.hs
@@ -0,0 +1,54 @@
+module Hob.Command.FocusNextTabSpec (main, spec) where
+
+import Control.Monad   (replicateM_)
+import Graphics.UI.Gtk
+
+import           Hob.Command.FocusNextTab
+import           Hob.Command.NewTab
+import           Hob.Context
+import qualified Hob.Context.UiContext    as HC
+
+import Test.Hspec
+
+import HobTest.Context.Default
+import HobTest.Control
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "focus next tab command" $ do
+    it "does not change the state if there are no tabs" $ do
+      ctx <- loadGuiWithNTabs 0
+      currentPage <- focusNextTab ctx
+      currentPage `shouldBe` -1
+
+    it "leaves the first tab focused if there is just one tab" $ do
+      ctx <- loadGuiWithNTabs 1
+      currentPage <- focusNextTab ctx
+      currentPage `shouldBe` 0
+
+    it "focuses the next tab" $ do
+      ctx <- loadGuiWithNTabs 2
+      notebookSetCurrentPage (HC.mainNotebook . uiContext $ ctx) 0
+      currentPage <- focusNextTab ctx
+      currentPage `shouldBe` 1
+
+    it "focuses the first tab if the command is invoked on the last tab" $ do
+      ctx <- loadGuiWithNTabs 2
+      notebookSetCurrentPage (HC.mainNotebook . uiContext $ ctx) 1
+      currentPage <- focusNextTab ctx
+      currentPage `shouldBe` 0
+
+focusNextTab :: Context -> IO Int
+focusNextTab ctx = do
+    runCtxActions ctx $ commandExecute focusNextTabCommandHandler
+    let notebook = HC.mainNotebook . uiContext $ ctx
+    notebookGetCurrentPage notebook
+
+loadGuiWithNTabs :: Int -> IO Context
+loadGuiWithNTabs n = do
+    ctx <- loadDefaultContext
+    replicateM_ n $ runCtxActions ctx editNewFile
+    return ctx
diff --git a/test/Hob/Command/FocusNumberedTabSpec.hs b/test/Hob/Command/FocusNumberedTabSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Command/FocusNumberedTabSpec.hs
@@ -0,0 +1,43 @@
+module Hob.Command.FocusNumberedTabSpec (main, spec) where
+
+import Control.Monad   (replicateM_)
+import Graphics.UI.Gtk
+
+import           Hob.Command.FocusNumberedTab
+import           Hob.Command.NewTab
+import           Hob.Context
+import qualified Hob.Context.UiContext        as HC
+
+import Test.Hspec
+
+import HobTest.Context.Default
+import HobTest.Control
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "focus numbered tab command" $ do
+    it "does not change the state if there is no matching tab" $ do
+      ctx <- loadGuiWithNTabs 0
+      currentPage <- focusNumberedTab 2 ctx
+      currentPage `shouldBe` -1
+
+    it "focuses the numbered tab" $ do
+      ctx <- loadGuiWithNTabs 3
+      notebookSetCurrentPage (HC.mainNotebook . uiContext $ ctx) 0
+      currentPage <- focusNumberedTab 2 ctx
+      currentPage `shouldBe` 2
+
+focusNumberedTab :: Int -> Context -> IO Int
+focusNumberedTab nr ctx = do
+    runCtxActions ctx $ commandExecute (focusNumberedTabCommandHandler nr)
+    let notebook = HC.mainNotebook . uiContext $ ctx
+    notebookGetCurrentPage notebook
+
+loadGuiWithNTabs :: Int -> IO Context
+loadGuiWithNTabs n = do
+    ctx <- loadDefaultContext
+    replicateM_ n $ runCtxActions ctx editNewFile
+    return ctx
diff --git a/test/Hob/Command/FocusPreviousTabSpec.hs b/test/Hob/Command/FocusPreviousTabSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Command/FocusPreviousTabSpec.hs
@@ -0,0 +1,54 @@
+module Hob.Command.FocusPreviousTabSpec (main, spec) where
+
+import Control.Monad   (replicateM_)
+import Graphics.UI.Gtk
+
+import           Hob.Command.FocusPreviousTab
+import           Hob.Command.NewTab
+import           Hob.Context
+import qualified Hob.Context.UiContext        as HC
+
+import Test.Hspec
+
+import HobTest.Context.Default
+import HobTest.Control
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "focus previous tab command" $ do
+    it "does not change the state if there are no tabs" $ do
+      ctx <- loadGuiWithNTabs 0
+      currentPage <- focusPreviousTab ctx
+      currentPage `shouldBe` -1
+
+    it "leaves the first tab focused if there is just one tab" $ do
+      ctx <- loadGuiWithNTabs 1
+      currentPage <- focusPreviousTab ctx
+      currentPage `shouldBe` 0
+
+    it "focuses the previous tab" $ do
+      ctx <- loadGuiWithNTabs 2
+      notebookSetCurrentPage (HC.mainNotebook . uiContext $ ctx) 1
+      currentPage <- focusPreviousTab ctx
+      currentPage `shouldBe` 0
+
+    it "focuses the last tab if the command is invoked on the first tab" $ do
+      ctx <- loadGuiWithNTabs 2
+      notebookSetCurrentPage (HC.mainNotebook . uiContext $ ctx) 0
+      currentPage <- focusPreviousTab ctx
+      currentPage `shouldBe` 1
+
+focusPreviousTab :: Context -> IO Int
+focusPreviousTab ctx = do
+    runCtxActions ctx $ commandExecute focusPreviousTabCommandHandler
+    let notebook = HC.mainNotebook . uiContext $ ctx
+    notebookGetCurrentPage notebook
+
+loadGuiWithNTabs :: Int -> IO Context
+loadGuiWithNTabs n = do
+    ctx <- loadDefaultContext
+    replicateM_ n $ runCtxActions ctx editNewFile
+    return ctx
diff --git a/test/Hob/Command/FocusSidebarSpec.hs b/test/Hob/Command/FocusSidebarSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Command/FocusSidebarSpec.hs
@@ -0,0 +1,55 @@
+module Hob.Command.FocusSidebarSpec (main, spec) where
+
+import Graphics.UI.Gtk
+
+import           Hob.Command.FocusSidebar
+import           Hob.Context
+import qualified Hob.Context.UiContext    as HC
+
+import Test.Hspec
+
+import HobTest.Context.Default
+import HobTest.Context.Stubbed
+import HobTest.Control
+import HobTest.Editor
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "focus sidebar" $ do
+    it "focuses the sidebar" $ do
+      ctx <- loadDefaultContext
+      focused <- focusSidebarAndReturnState ctx
+      focused `shouldBe` True
+
+    it "can sync the open editor path in sidebar" $ do
+      ctx <- loadStubbedContext
+      launchEditorTab ctx "/xxx/a/b"
+      path <- syncSidebarAndReturnSelectedPath ctx
+      focused <- sidebarFocusState ctx
+      path `shouldBe` [0, 0]
+      focused `shouldBe` True
+
+    it "can sync the open editor path in sidebar when there is a directory named as the file's basename" $ do
+      ctx <- loadStubbedContext
+      launchEditorTab ctx "/xxx/a.b"
+      path <- syncSidebarAndReturnSelectedPath ctx
+      focused <- sidebarFocusState ctx
+      path `shouldBe` [1]
+      focused `shouldBe` True
+
+sidebarFocusState :: Context -> IO Bool
+sidebarFocusState = widgetGetIsFocus . HC.sidebarTree . uiContext
+
+focusSidebarAndReturnState :: Context -> IO Bool
+focusSidebarAndReturnState ctx = do
+    runCtxActions ctx $ commandExecute focusSidebarCommandHandler
+    sidebarFocusState ctx
+
+syncSidebarAndReturnSelectedPath :: Context -> IO TreePath
+syncSidebarAndReturnSelectedPath ctx = do
+    runCtxActions ctx $ commandExecute syncFocusSidebarCommandHandler
+    pos <- treeViewGetCursor . HC.sidebarTree . uiContext $ ctx
+    return $ fst pos
diff --git a/test/Hob/Command/NewTabSpec.hs b/test/Hob/Command/NewTabSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Command/NewTabSpec.hs
@@ -0,0 +1,35 @@
+module Hob.Command.NewTabSpec (main, spec) where
+
+import Test.Hspec
+
+import Graphics.UI.Gtk
+
+import qualified Hob.Context           as HC
+import qualified Hob.Context.UiContext as HC
+
+import HobTest.Context.Stubbed
+import HobTest.Editor
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "new tab command" $ do
+    it "creates a new unnamed file" $ do
+      ctx <- launchNewFile
+      pagesAfterActivatingDirectory <- getNumberOfEditorPages ctx
+      pagesAfterActivatingDirectory `shouldBe` 1
+
+    it "focuses the tab with the open file if requested to open an already loaded file" $ do
+      ctx <- loadStubbedContext
+      let notebook = HC.mainNotebook . HC.uiContext $ ctx
+      launchEditorTab ctx "/xxx/testName.hs"
+      currentPageOfFirstLoadedFile <- notebookGetCurrentPage notebook
+      launchEditorTab ctx "/xxx/c"
+      pagesBeforeOpeningExistingFile <- notebookGetNPages notebook
+      launchEditorTab ctx "/xxx/testName.hs"
+      currentPageAfterLoadingTheFirstLoadedFile <- notebookGetCurrentPage notebook
+      pagesAfterOpeningExistingFile <- notebookGetNPages notebook
+      pagesAfterOpeningExistingFile `shouldBe` pagesBeforeOpeningExistingFile
+      currentPageAfterLoadingTheFirstLoadedFile `shouldBe` currentPageOfFirstLoadedFile
diff --git a/test/Hob/Command/ReloadSidebarTreeSpec.hs b/test/Hob/Command/ReloadSidebarTreeSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Command/ReloadSidebarTreeSpec.hs
@@ -0,0 +1,69 @@
+module Hob.Command.ReloadSidebarTreeSpec (main, spec) where
+
+import Data.Maybe
+import Data.Tree
+import Graphics.UI.Gtk
+
+import           Hob.Command.ReloadSidebarTree
+import           Hob.Context
+import qualified Hob.Context.FileContext       as HFC
+import qualified Hob.Context.UiContext         as HC
+import           Hob.DirectoryTree
+
+import Test.Hspec
+
+import HobTest.Context.Stubbed
+import HobTest.Control
+import HobTest.Sidebar
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "reload sidebar tree command" $ do
+    it "loads new items in the tree" $ do
+      ctx <- loadStubbedContext
+      values <- getDirectoryListingSidebarRootItems ctx
+      values `shouldBe` ["a", "a.b", "c", "-"]
+      let ctx' = replaceStubbedTree ctx
+      runCtxActions ctx' $ commandExecute reloadSidebarTreeCommandHandler
+      values' <- getDirectoryListingSidebarRootItems ctx'
+      values' `shouldBe` ["b","c"]
+
+    it "reload sidebar search index" $ do
+      ctx <- loadStubbedContext
+      cursorShouldBeOnAfterSearch ctx "/new/" []
+      let ctx' = replaceStubbedTree ctx
+      runCtxActions ctx' $ commandExecute reloadSidebarTreeCommandHandler
+      cursorShouldBeOnAfterSearch ctx "/new/" [1, 0]
+
+replaceStubbedTree :: Context -> Context
+replaceStubbedTree ctx = ctx{fileContext = newFileContext}
+    where
+        newFileContext = (fileContext ctx){HFC.contextFileTreeLoader = altFileTreeStub}
+
+altFileTreeStub :: IO (Forest DirectoryTreeElement)
+altFileTreeStub = return [
+    Node (DirectoryTreeElement "b" "/xxx/b" False) [],
+    Node (DirectoryTreeElement "c" "/xxx/c" True) [
+        Node (DirectoryTreeElement "new" "/xxx/c/new" False) []
+    ]]
+
+getDirectoryListingSidebarRootItems :: Context -> IO [String]
+getDirectoryListingSidebarRootItems ctx = do
+    let treeView = HC.sidebarTree . uiContext $ ctx
+    maybeTreeModel <- treeViewGetModel treeView
+    let treeModel = fromJust maybeTreeModel
+    maybeRoot <- treeModelGetIter treeModel [0]
+    getSiblingItems treeModel maybeRoot
+
+getSiblingItems :: TreeModel -> Maybe TreeIter -> IO [String]
+getSiblingItems _ Nothing = return []
+getSiblingItems treeModel (Just iter) = do
+    let colId = makeColumnIdString 0
+    value <- treeModelGetValue treeModel iter colId
+    siblings <- getSiblingItems treeModel =<< treeModelIterNext treeModel iter
+    return $ value : siblings
+
+
diff --git a/test/Hob/Command/ReplaceTextSpec.hs b/test/Hob/Command/ReplaceTextSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Command/ReplaceTextSpec.hs
@@ -0,0 +1,102 @@
+module Hob.Command.ReplaceTextSpec (main, spec) where
+
+import Control.Monad.State (liftIO)
+import Data.IORef
+import Data.Maybe
+import Data.Text           (pack)
+import Graphics.UI.Gtk
+import Test.Hspec
+
+import           Hob.Command.ReplaceText
+import           Hob.Context
+import qualified Hob.Context.UiContext   as HC
+import           Hob.Ui.Editor           (getActiveEditor, newEditorForText)
+
+import HobTest.Context.Default
+import HobTest.Control
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+  describe "replace text command matcher" $ do
+    it "accepts when command starts with the configured letter" $ do
+        let handler _ _ = CommandHandler Nothing (return())
+        let matcher = createMatcherForReplace 's' handler
+        isJust (matchCommand matcher "s//") `shouldBe` True
+
+    it "does not accept when command starts with another than the configured letter" $ do
+        let handler _ _ = CommandHandler Nothing (return())
+        let matcher = createMatcherForReplace 's' handler
+        isJust (matchCommand matcher "x//") `shouldBe` False
+
+    it "does not accept when command is empty" $ do
+        let handler _ _ = CommandHandler Nothing (return())
+        let matcher = createMatcherForReplace 's' handler
+        isJust (matchCommand matcher "") `shouldBe` False
+
+    it "parses search and replace strings" $ do
+        let handler "S" "R" = CommandHandler Nothing (return())
+            handler s r = error $ "unexpected invokation with \""++s++ "\" / \""++r++"\""
+            matcher = createMatcherForReplace 'x' handler
+            matchResult = matchCommand matcher "x/S/R"
+        ctx <- loadDefaultContext
+        runCtxActions ctx $ commandExecute (fromJust matchResult)
+
+    it "ignores options when parsing search and replace strings" $ do
+        let handler "S" "R" = CommandHandler Nothing (return())
+            handler s r = error $ "unexpected invokation with \""++s++ "\" / \""++r++"\""
+            matcher = createMatcherForReplace 'x' handler
+            matchResult = matchCommand matcher "x#S#R#options"
+        ctx <- loadDefaultContext
+        runCtxActions ctx $ commandExecute (fromJust matchResult)
+
+    it "matches escaped separator char as text" $ do
+        let handler "S/S" "R/R" = CommandHandler Nothing (return())
+            handler s r = error $ "unexpected invokation with \""++s++ "\" / \""++r++"\""
+            matcher = createMatcherForReplace 'x' handler
+            matchResult = matchCommand matcher "x/S\\/S/R\\/R"
+        ctx <- loadDefaultContext
+        runCtxActions ctx $ commandExecute (fromJust matchResult)
+
+    it "matches escaped any char as escape seq and text" $ do
+        let handler "S\\xS" "R\\xR" = CommandHandler Nothing (return())
+            handler s r = error $ "unexpected invokation with \""++s++ "\" / \""++r++"\""
+            matcher = createMatcherForReplace 'x' handler
+            matchResult = matchCommand matcher "x/S\\xS/R\\xR"
+        ctx <- loadDefaultContext
+        runCtxActions ctx $ commandExecute (fromJust matchResult)
+
+    it "matches escaped end of string as text" $ do
+        let handler "S" "R\\" = CommandHandler Nothing (return())
+            handler s r = error $ "unexpected invokation with \""++s++ "\" / \""++r++"\""
+            matcher = createMatcherForReplace 'x' handler
+            matchResult = matchCommand matcher "x/S/R\\"
+        ctx <- loadDefaultContext
+        runCtxActions ctx $ commandExecute (fromJust matchResult)
+
+  describe "replace text command handler" $ do
+    it "focuses editor on execute" $ do
+        (ctx, _) <- loadGuiWithEditor
+        runCtxActions ctx $ commandExecute (replaceCommandHandler "text" ":)")
+        editorFocused <- widgetGetIsFocus . fromJust =<< getActiveEditor ctx
+        editorFocused `shouldBe` True
+
+    it "sets replace mode" $ do
+        (ctx, _) <- loadGuiWithEditor
+        runCtxActions ctx $ commandExecute (replaceCommandHandler "text" ":)")
+        ref <- newIORef Nothing
+        runCtxActions ctx $ activeModes >>= (liftIO . writeIORef ref)
+        modes <- readIORef ref
+        (map modeName . fromJust) modes `shouldBe` ["replace"]
+
+loadGuiWithEditor :: IO (Context, TextBuffer)
+loadGuiWithEditor = do
+    ctx <- loadDefaultContext
+    let notebook = HC.mainNotebook . uiContext $ ctx
+    runCtxActions ctx $ newEditorForText notebook Nothing $ pack "text - initial text! text"
+    mEditor <- getActiveEditor ctx
+    let editor = fromJust mEditor
+    buffer <- textViewGetBuffer editor
+    return (ctx, buffer)
diff --git a/test/Hob/Command/SaveCurrentTabSpec.hs b/test/Hob/Command/SaveCurrentTabSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Command/SaveCurrentTabSpec.hs
@@ -0,0 +1,108 @@
+module Hob.Command.SaveCurrentTabSpec (main, spec) where
+
+import Data.IORef
+import Data.Maybe
+import Data.Text       (Text, pack)
+import Graphics.UI.Gtk
+
+import           Hob.Command.NewTab
+import           Hob.Command.SaveCurrentTab
+import           Hob.Context
+import qualified Hob.Context.FileContext    as HFC
+import qualified Hob.Context.StyleContext   as HSC
+import qualified Hob.Context.UiContext      as HUC
+import           Hob.Control
+import           Hob.Ui
+
+import Test.Hspec
+
+import HobTest.Context.Default
+import HobTest.Context.Stubbed
+import HobTest.Control
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "save current tab command" $ do
+    it "saves the currently active file" $ do
+      (mockedWriter, mockReader) <- mockedFileWriter
+      sc <- HSC.defaultStyleContext "app-data"
+      fc <- HFC.defaultFileContext stubbedFileLoader mockedWriter emptyFileTree
+      ctx <- launchFileInContext fc sc "/xxx/testName.hs"
+      runCtxActions ctx $ saveCurrentEditorTabHandler emptyFileChooser
+      savedFile <- mockReader
+      savedFile `shouldBe` Just ("/xxx/testName.hs", pack "file contents for /xxx/testName.hs")
+
+    it "skips save when there is no active file" $ do
+      sc <- HSC.defaultStyleContext "app-data"
+      fc <- HFC.defaultFileContext emptyFileLoader failingFileWriter emptyFileTree
+      ctx <- loadGui fc sc
+      runCtxActions ctx $ saveCurrentEditorTabHandler emptyFileChooser
+
+    it "marks buffer as unmodified on save" $ do
+      sc <- HSC.defaultStyleContext "app-data"
+      fc <- HFC.defaultFileContext stubbedFileLoader blackholeFileWriter emptyFileTree
+      ctx <- launchFileInContext fc sc "/xxx/testName.hs"
+      flushEvents
+      buffer <- textViewGetBuffer . fromJust =<< getActiveEditor ctx
+      textBufferSetModified buffer True
+      runCtxActions ctx $ saveCurrentEditorTabHandler emptyFileChooser
+      stateAfterSave <- textBufferGetModified buffer
+      stateAfterSave `shouldBe` False
+
+    it "requests filename for a new file" $ do
+      (mockedWriter, mockReader) <- mockedFileWriter
+      sc <- HSC.defaultStyleContext "app-data"
+      fc <- HFC.defaultFileContext emptyFileLoader mockedWriter emptyFileTree
+      ctx <- launchNewFileInContextAndSaveAs fc sc "/xxx/fileResponded.hs"
+      savedFile <- mockReader
+      savedFile `shouldBe` Just ("/xxx/fileResponded.hs", pack "")
+      tabText <- getActiveEditorTabText ctx
+      tabText `shouldBe` "fileResponded.hs"
+
+    it "updates the tab title from the newly got filepath" $ do
+      sc <- HSC.defaultStyleContext "app-data"
+      fc <- HFC.defaultFileContext emptyFileLoader blackholeFileWriter emptyFileTree
+      ctx <- launchNewFileInContextAndSaveAs fc sc "/xxx/fileResponded.hs"
+      buffer <- textViewGetBuffer . fromJust =<< getActiveEditor ctx
+      textBufferSetModified buffer True
+      tabText <- getActiveEditorTabText ctx
+      tabText `shouldBe` "fileResponded.hs*"
+
+launchFileInContext :: HFC.FileContext -> HSC.StyleContext -> String -> IO Context
+launchFileInContext fileCtx styleCtx filename = do
+    ctx <- loadGui fileCtx styleCtx
+    launchEditorTab ctx filename
+    return ctx
+
+launchNewFileInContextAndSaveAs :: HFC.FileContext -> HSC.StyleContext -> String -> IO Context
+launchNewFileInContextAndSaveAs fileCtx styleCtx filename = do
+    ctx <- loadGui fileCtx styleCtx
+    runCtxActions ctx editNewFile
+    runCtxActions ctx $ saveCurrentEditorTabHandler (stubbedFileChooser $ Just filename)
+    return ctx
+
+launchEditorTab :: Context -> String -> IO ()
+launchEditorTab ctx file = do
+    let notebook = HUC.mainNotebook . uiContext $ ctx
+    launchNewFileEditor ctx notebook file
+
+getActiveEditorTabText :: Context  -> IO String
+getActiveEditorTabText ctx = do
+    let notebook = HUC.mainNotebook . uiContext $ ctx
+    currentlyActiveEditor <- getActiveEditorTab ctx
+    text <- notebookGetTabLabelText notebook $ fromJust currentlyActiveEditor
+    return $ fromJust text
+
+mockedFileWriter :: IO (HFC.FileWriter, IO (Maybe (String, Text)))
+mockedFileWriter = do
+    recorder <- newIORef Nothing
+    return (curry $ writeIORef recorder . Just, readIORef recorder)
+
+stubbedFileChooser :: Maybe FilePath -> NewFileNameChooser
+stubbedFileChooser = return
+
+emptyFileChooser :: NewFileNameChooser
+emptyFileChooser = stubbedFileChooser Nothing
diff --git a/test/Hob/Context/FileContextSpec.hs b/test/Hob/Context/FileContextSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Context/FileContextSpec.hs
@@ -0,0 +1,23 @@
+module Hob.Context.FileContextSpec (main, spec) where
+
+import Graphics.UI.Gtk.SourceView (sourceLanguageGetName)
+import Hob.Context.FileContext
+import Test.Hspec
+
+import HobTest.Context.Default
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "sourceLanguage" $
+    it "guesses language based on the file ending" $ do
+      ctx <- stubbedCtx
+      Just lang <- sourceLanguage ctx "/tmp/yyy/test.hs"
+      name <- sourceLanguageGetName lang
+      name `shouldBe` "Haskell"
+
+stubbedCtx :: IO FileContext
+stubbedCtx = defaultFileContext emptyFileLoader blackholeFileWriter emptyFileTree
+
diff --git a/test/Hob/Context/StyleContextSpec.hs b/test/Hob/Context/StyleContextSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Context/StyleContextSpec.hs
@@ -0,0 +1,30 @@
+module Hob.Context.StyleContextSpec (main, spec) where
+
+import Graphics.UI.Gtk.SourceView (sourceStyleSchemeGetName)
+import Hob.Context.StyleContext
+import Test.Hspec
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+  describe "uiFile" $
+    it "returns prefixed path" $ do
+      ctx <- stubbedCtx
+      uiFile ctx `shouldBe` "app-data/ui.glade"
+
+  describe "uiTheme" $
+    it "returns prefixed path" $ do
+      ctx <- stubbedCtx
+      uiTheme ctx `shouldBe` "app-data/themes/gtk/default/gtk-dark.css"
+
+  describe "sourceStyleScheme" $
+    it "returns source style to use" $ do
+      ctx <- stubbedCtx
+      Just style <- sourceStyleScheme ctx $ Just "/tmp/yyy/test.hs"
+      name <- sourceStyleSchemeGetName style
+      name `shouldBe` "Molokai"
+
+stubbedCtx :: IO StyleContext
+stubbedCtx = defaultStyleContext "app-data"
diff --git a/test/Hob/ContextSpec.hs b/test/Hob/ContextSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/ContextSpec.hs
@@ -0,0 +1,276 @@
+module Hob.ContextSpec (main, spec) where
+
+import Control.Monad.Reader
+import Data.IORef
+import Data.Maybe           (fromJust, isJust, isNothing)
+import Data.Monoid
+import Graphics.UI.Gtk      (Modifier (..))
+
+import Hob.Context
+
+import Test.Hspec
+
+import HobTest.Context.Default
+import HobTest.Control
+
+
+dummyEditor :: Editor
+dummyEditor = Editor
+            { editorId = \_ -> return 1
+            , enterEditorMode = \editor mode -> do
+                    currentModes <- runOnEditor modeStack editor
+                    return $ editor{modeStack = \_ -> return (currentModes ++ [mode])}
+            , exitLastEditorMode = \editor -> do
+                    currentModes <- runOnEditor modeStack editor
+                    return $ editor{modeStack = \_ -> return (init currentModes)}
+            , modeStack      = \_ -> return []
+            , isCurrentlyActive  = \_ -> return True
+            }
+
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+    describe "command matcher" $ do
+        describe "mempty" $ do
+            it "does not return any commands on matching a command" $
+                expectNoCommandHandler $ matchCommand mempty "/asd"
+
+            it "does not return any commands on matching a key binding" $
+                expectNoCommandHandler $ matchKeyBinding mempty ([Control], "S")
+
+        it "combines command matchers on mappend with the identity on the left" $ do
+            let matcher = matcherForCommand "test"
+            let combinedMatcher = mempty `mappend` matcher
+            expectCommandHandler $ matchCommand combinedMatcher "test"
+
+        it "combines key binding matchers on mappend with the identity on the left" $ do
+            let matcher = matcherForKeyBinding ([Control], "S")
+            let combinedMatcher = mempty `mappend` matcher
+            expectCommandHandler $ matchKeyBinding combinedMatcher ([Control], "S")
+
+        it "combines command matchers on mappend with the identity on the right" $ do
+            let matcher = matcherForCommand "test"
+            let combinedMatcher = matcher `mappend` mempty
+            expectCommandHandler $ matchCommand combinedMatcher "test"
+
+        it "combines key binding matchers on mappend with the identity on the right" $ do
+            let matcher = matcherForKeyBinding ([Control], "S")
+            let combinedMatcher = matcher `mappend` mempty
+            expectCommandHandler $ matchKeyBinding combinedMatcher ([Control], "S")
+
+        it "combines command matchers assiciatively" $ do
+            let matcher1 = matcherForCommand "test1"
+            let matcher2 = matcherForCommand "test2"
+            let matcher3 = matcherForCommand "test3"
+            let combinedMatcher1 = (matcher1 `mappend` matcher2) `mappend` matcher3
+            let combinedMatcher2 = matcher1 `mappend` (matcher2 `mappend` matcher3)
+            expectCommandHandler $ matchCommand combinedMatcher1 "test1"
+            expectCommandHandler $ matchCommand combinedMatcher1 "test2"
+            expectCommandHandler $ matchCommand combinedMatcher1 "test3"
+            expectCommandHandler $ matchCommand combinedMatcher2 "test1"
+            expectCommandHandler $ matchCommand combinedMatcher2 "test2"
+            expectCommandHandler $ matchCommand combinedMatcher2 "test3"
+            expectNoCommandHandler $ matchCommand combinedMatcher1 "test"
+            expectNoCommandHandler $ matchCommand combinedMatcher2 "test"
+
+        describe "command matcher for prefix" $ do
+            it "does not match unknown prefix command" $ do
+                let matcher = createMatcherForPrefix "/" $ const emptyHandler
+                let matchedHandler = matchCommand matcher "%test text"
+                isNothing matchedHandler `shouldBe` True
+
+            it "matches known prefix command" $ do
+                handledText <- executeMockedMatcher "/" "/test text"
+                handledText `shouldBe` Just "test text"
+
+            it "matches known prefix command with no text" $ do
+                handledText <- executeMockedMatcher "/" "/"
+                handledText `shouldBe` Just ""
+
+        describe "command matcher for key binding" $ do
+            it "does not match unknown key binding" $ do
+                let matcher = createMatcherForKeyBinding ([Control], "S") emptyHandler
+                let matchedHandler = matchKeyBinding matcher ([Control], "X")
+                isNothing matchedHandler `shouldBe` True
+
+            it "matches known key binding" $ do
+                ctx <- loadDefaultContext
+                (handler, readHandledText) <- recordingHandler
+                let matcher = createMatcherForKeyBinding ([Control], "S") $ handler "test"
+                let matchedHandler = matchKeyBinding matcher ([Control], "S")
+                handledText <- executeRecordingHandler ctx matchedHandler readHandledText
+                handledText `shouldBe` Just "test"
+
+        describe "command matcher text command" $ do
+            it "does not match unknown text command" $ do
+                let matcher = createMatcherForCommand "hi" emptyHandler
+                let matchedHandler = matchCommand matcher "ho"
+                isNothing matchedHandler `shouldBe` True
+
+            it "matches known command" $ do
+                ctx <- loadDefaultContext
+                (handler, readHandledText) <- recordingHandler
+                let matcher = createMatcherForCommand "hi" $ handler "test"
+                let matchedHandler = matchCommand matcher "hi"
+                handledText <- executeRecordingHandler ctx matchedHandler readHandledText
+                handledText `shouldBe` Just "test"
+
+    describe "app runner" $ do
+        it "runs empty monad" $ do
+            ctx <- loadDefaultContext
+            runCtxActions ctx $ return ()
+
+        it "can defer command execution to IO" $ do
+            record <- newIORef (0::Int)
+            ctx <- loadDefaultContext
+            let countingCommand = liftIO $ modifyIORef record (+1)
+            let deferCommand cmd = liftIO $ runCtxActions ctx cmd
+            deferCommand (countingCommand >>
+                deferCommand (countingCommand >>
+                    deferCommand countingCommand))
+            invokeCount <- readIORef record
+            invokeCount `shouldBe` (3::Int)
+
+    describe "context commands" $ do
+        it "returns Nothing for list of modes if there are no editors" $ do
+            ctx <- loadContextWithEditors []
+            runCtxActions ctx $ enterMode $ Mode "testmode" mempty $ return()
+            modes <- getActiveModes ctx
+            isNothing modes `shouldBe` True
+
+        it "enters given mode to the active editor" $ do
+            ctx <- loadContextWithEditors [dummyEditor]
+            runCtxActions ctx $ enterMode $ Mode "testmode" mempty $ return()
+            modes <- getActiveModes ctx
+            (modeName . last . fromJust) modes `shouldBe` "testmode"
+
+        it "removes mode from the active editor on exitMode" $ do
+            ctx <- loadContextWithEditors [dummyEditor]
+            runCtxActions ctx $ enterMode $ Mode "testmode" mempty $ return()
+            runCtxActions ctx exitLastMode
+            modes <- getActiveModes ctx
+            (length . fromJust) modes `shouldBe` 0
+
+        it "emits mode change event on entering a mode" $ do
+            record <- newIORef False
+            ctx <- loadContextWithEditors [dummyEditor]
+            runCtxActions ctx $ registerEventHandler (Event "core.mode.change") (liftIO $ writeIORef record True)
+            runCtxActions ctx $ enterMode $ Mode "testmode" mempty $ return()
+            invoked <- readIORef record
+            invoked `shouldBe` True
+
+        it "emits mode change event on exiting a mode" $ do
+            record <- newIORef False
+            ctx <- loadContextWithEditors [dummyEditor]
+            runCtxActions ctx $ enterMode $ Mode "testmode" mempty $ return()
+            runCtxActions ctx $ registerEventHandler (Event "core.mode.change") (liftIO $ writeIORef record True)
+            runCtxActions ctx exitLastMode
+            invoked <- readIORef record
+            invoked `shouldBe` True
+
+    describe "active command matcher retriever" $ do
+        it "retains base command matcher" $ do
+            ctx <- loadContextWithEditors [dummyEditor]
+            runCtxActions ctx $ enterMode $ Mode "testmode" mempty $ return()
+            commands <- runApp ctx getActiveCommands
+            let cmd = matchKeyBinding commands ([Control], "Tab")
+            isJust cmd `shouldBe` True
+
+        it "retrieves active mode command matcher" $ do
+            ctx <- loadContextWithEditors [dummyEditor]
+            readHandledText <- enterRecordingMode ctx "testMode1" "hi" "test"
+            handledText <- executeActiveRecordingHandler ctx "hi" readHandledText
+            handledText `shouldBe` Just "test"
+
+        it "retrieves last active mode command first" $ do
+            ctx <- loadContextWithEditors [dummyEditor]
+            _ <- enterRecordingMode ctx "testMode1" "hi" "test1"
+            readHandledText <- enterRecordingMode ctx "testMode2" "hi" "test2"
+            handledText <- executeActiveRecordingHandler ctx "hi" readHandledText
+            handledText `shouldBe` Just "test2"
+
+    describe "event handler support" $ do
+        it "invokes a registered handler on fireEvent" $ do
+            record <- newIORef False
+            ctx <- loadDefaultContext
+            runCtxActions ctx $ registerEventHandler (Event "test.event") (liftIO $ writeIORef record True)
+            runCtxActions ctx $ emitEvent (Event "test.event")
+            invoked <- readIORef record
+            invoked `shouldBe` True
+
+        it "does not invoke an event handler with different name" $ do
+            record <- newIORef False
+            ctx <- loadDefaultContext
+            runCtxActions ctx $ registerEventHandler (Event "test.event1") (liftIO $ writeIORef record True)
+            runCtxActions ctx $ emitEvent (Event "test.event2")
+            invoked <- readIORef record
+            invoked `shouldBe` False
+
+loadContextWithEditors :: [Editor] -> IO Context
+loadContextWithEditors newEditors = do
+    ctx <- loadDefaultContext
+    updateEditors (editors ctx) $ const $ return newEditors
+    return ctx
+
+getActiveModes :: Context -> IO (Maybe [Mode])
+getActiveModes ctx = do
+    record <- newIORef Nothing
+    runCtxActions ctx $ activeModes >>= (liftIO . writeIORef record . Just)
+    Just modes <- readIORef record
+    return modes
+
+executeMockedMatcher :: String -> String -> IO (Maybe String)
+executeMockedMatcher prefix text = do
+    ctx <- loadDefaultContext
+    (handler, readHandledText) <- recordingHandler
+    let matcher = createMatcherForPrefix prefix handler
+    let matchedHandler = matchCommand matcher text
+    runCtxActions ctx $ commandExecute $ fromJust matchedHandler
+    readHandledText
+
+expectCommandHandler :: Maybe CommandHandler -> Expectation
+expectCommandHandler cmdHandler = isNothing cmdHandler `shouldBe` False
+
+expectNoCommandHandler :: Maybe CommandHandler -> Expectation
+expectNoCommandHandler cmdHandler = isNothing cmdHandler `shouldBe` True
+
+matcherForCommand :: String -> CommandMatcher
+matcherForCommand command = CommandMatcher (const Nothing) $ emptyCommandHandlerForCommand command
+    where emptyCommandHandlerForCommand cmd x = if x == cmd then Just emptyHandler else Nothing
+
+matcherForKeyBinding :: KeyboardBinding -> CommandMatcher
+matcherForKeyBinding key = CommandMatcher (emptyCommandHandlerForKey key) (const Nothing)
+    where emptyCommandHandlerForKey k x = if x == k then Just emptyHandler else Nothing
+
+emptyHandler :: CommandHandler
+emptyHandler = CommandHandler Nothing (return())
+
+recordingHandler :: IO (String -> CommandHandler, IO (Maybe String))
+recordingHandler = do
+    record <- newIORef Nothing
+    return (
+                \params -> CommandHandler Nothing (liftIO $ writeIORef record $ Just params),
+                readIORef record
+            )
+
+executeRecordingHandler :: Context -> Maybe CommandHandler -> IO (Maybe String) -> IO (Maybe String)
+executeRecordingHandler ctx handler readHandledText = do
+    runCtxActions ctx $ commandExecute $ fromJust handler
+    readHandledText
+
+executeActiveRecordingHandler :: Context -> String -> IO (Maybe String) -> IO (Maybe String)
+executeActiveRecordingHandler ctx cmd readHandledText = do
+    commands <- runApp ctx getActiveCommands
+    let command = matchCommand commands cmd
+    executeRecordingHandler ctx command readHandledText
+
+enterRecordingMode :: Context -> String -> String -> String -> IO (IO (Maybe String))
+enterRecordingMode ctx modename cmd resp = do
+    (handler, readHandledText) <- recordingHandler
+    let matcher = createMatcherForCommand cmd $ handler resp
+    runApp ctx $ enterMode $ Mode modename matcher $ return()
+    return readHandledText
+
diff --git a/test/Hob/ControlSpec.hs b/test/Hob/ControlSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/ControlSpec.hs
@@ -0,0 +1,30 @@
+module Hob.ControlSpec (main, spec) where
+
+import Data.IORef
+
+import Test.Hspec
+
+import Hob.Control
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "maybeDo" $ do
+    it "does not execute on Nothing" $ do
+        (executor, mockReader) <- mockedExecutor
+        maybeDo executor Nothing
+        executionStatus <- mockReader
+        executionStatus `shouldBe` Nothing
+
+    it "passes unwrapped value on Just" $ do
+        (executor, mockReader) <- mockedExecutor
+        maybeDo executor $ Just "test value"
+        executionStatus <- mockReader
+        executionStatus `shouldBe` Just "test value"
+
+mockedExecutor :: IO (String -> IO(), IO (Maybe String))
+mockedExecutor = do
+    recorder <- newIORef Nothing
+    return (writeIORef recorder . Just, readIORef recorder)
diff --git a/test/Hob/DirectoryTreeSpec.hs b/test/Hob/DirectoryTreeSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/DirectoryTreeSpec.hs
@@ -0,0 +1,72 @@
+module Hob.DirectoryTreeSpec (main, spec) where
+
+import Control.Monad.Error
+import Data.Tree
+import Hob.DirectoryTree
+import Test.Hspec
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "fileTreeGenerator" $ do
+    it "returns directory contents" $ do
+        let dirContentsStub _ = return ["nd1", "nd2"]
+            isDirStub _ = return False
+            fileTreeGenerator' = fileTreeGenerator dirContentsStub isDirStub
+        items <- fileTreeGenerator' "/xxx/"
+        items `shouldBe` [Node (DirectoryTreeElement "nd1" "/xxx/nd1" False) [],
+                          Node (DirectoryTreeElement "nd2" "/xxx/nd2" False) []]
+
+    it "returns sub-directory contents" $ do
+        let dirContentsStub "/xxx" = return ["d1"]
+            dirContentsStub "/xxx/d1" = return ["nd2"]
+            dirContentsStub _ = throwError $ userError "unexpected directory listing"
+            isDirStub d = return $ "/xxx/d1" == d
+            fileTreeGenerator' = fileTreeGenerator dirContentsStub isDirStub
+        items <- fileTreeGenerator' "/xxx"
+        items `shouldBe` [Node (DirectoryTreeElement "d1" "/xxx/d1" True) [
+                          Node (DirectoryTreeElement "nd2" "/xxx/d1/nd2" False) []]]
+
+    it "hides dot (. | ..) directory contents" $ do
+        let dirContentsStub "/xxx" = return ["d1", ".", ".."]
+            dirContentsStub "/xxx/d1" = return ["nd2", ".", ".."]
+            dirContentsStub _ = throwError $ userError "unexpected directory listing"
+            isDirStub d = return $ "/xxx/d1" == d
+            fileTreeGenerator' = fileTreeGenerator dirContentsStub isDirStub
+        items <- fileTreeGenerator' "/xxx"
+        items `shouldBe` [Node (DirectoryTreeElement "d1" "/xxx/d1" True) [
+                          Node (DirectoryTreeElement "nd2" "/xxx/d1/nd2" False) []]]
+
+
+    it "hides .git directories" $ do
+        let dirContentsStub "/xxx" = return ["d1", ".git"]
+            dirContentsStub "/xxx/d1" = return ["nd2"]
+            dirContentsStub _ = throwError $ userError "unexpected directory listing"
+            isDirStub d = return $ "/xxx/d1" == d
+            fileTreeGenerator' = fileTreeGenerator dirContentsStub isDirStub
+        items <- fileTreeGenerator' "/xxx"
+        items `shouldBe` [Node (DirectoryTreeElement "d1" "/xxx/d1" True) [
+                          Node (DirectoryTreeElement "nd2" "/xxx/d1/nd2" False) []]]
+
+    it "sorts the directory files" $ do
+        let dirContentsStub _ = return ["nd2", "nd1"]
+            isDirStub _ = return False
+            fileTreeGenerator' = fileTreeGenerator dirContentsStub isDirStub
+        items <- fileTreeGenerator' "/xxx/"
+        items `shouldBe` [Node (DirectoryTreeElement "nd1" "/xxx/nd1" False) [],
+                          Node (DirectoryTreeElement "nd2" "/xxx/nd2" False) []]
+
+    it "sorts the directory subdirectories at the top" $ do
+        let dirContentsStub "/xxx/" = return ["nd1", "zd2", "zd1"]
+            dirContentsStub _ = return []
+            isDirStub "/xxx/zd1" = return True
+            isDirStub "/xxx/zd2" = return True
+            isDirStub _ = return False
+            fileTreeGenerator' = fileTreeGenerator dirContentsStub isDirStub
+        items <- fileTreeGenerator' "/xxx/"
+        items `shouldBe` [Node (DirectoryTreeElement "zd1" "/xxx/zd1" True) [],
+                          Node (DirectoryTreeElement "zd2" "/xxx/zd2" True) [],
+                          Node (DirectoryTreeElement "nd1" "/xxx/nd1" False) []]
+
diff --git a/test/Hob/Ui/CommandEntrySpec.hs b/test/Hob/Ui/CommandEntrySpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Ui/CommandEntrySpec.hs
@@ -0,0 +1,145 @@
+module Hob.Ui.CommandEntrySpec (main, spec) where
+
+import Control.Monad.Reader
+import Data.IORef
+import Graphics.UI.Gtk
+import Graphics.UI.Gtk.General.StyleContext
+
+import Hob.Context
+import Hob.Context.UiContext
+import Hob.Control
+import Hob.Ui.CommandEntry
+
+import Test.Hspec
+
+import HobTest.Context.Default
+import HobTest.Control
+
+type CommandHandlerReaders = (IO (Maybe String), IO (Maybe String), IO (Maybe String))
+type EntryApi = (App(), App())
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "command entry" $ do
+    it "is named" $ do
+      ctx <- loadDefaultContext
+      name <- widgetGetName $ commandEntry . uiContext $ ctx
+      name `shouldBe` "commandEntry"
+
+    it "initially there is no error class applied" $ do
+      ctx <- loadDefaultContext
+      styleCtx <- widgetGetStyleContext $ commandEntry . uiContext $ ctx
+      hasErrorClass <- styleContextHasClass styleCtx "error"
+      hasErrorClass `shouldBe` False
+
+    it "applies error style class if the command is unknown" $ do
+      ctx <- loadDefaultContext
+      let entry = commandEntry . uiContext $ ctx
+      entrySetText entry "qweqwe"
+      styleCtx <- widgetGetStyleContext entry
+      flushEvents
+      hasErrorClass <- styleContextHasClass styleCtx "error"
+      hasErrorClass `shouldBe` True
+
+    it "removes error style on empty command" $ do
+      ctx <- loadDefaultContext
+      let entry = commandEntry . uiContext $ ctx
+      entrySetText entry "not empty"
+      styleCtx <- widgetGetStyleContext entry
+      styleContextAddClass styleCtx "error"
+      entrySetText entry ""
+      flushEvents
+      hasErrorClass <- styleContextHasClass styleCtx "error"
+      hasErrorClass `shouldBe` False
+
+    it "removes error style on known command" $ do
+      (ctx, entry, entryApi, _) <- loadDefaultGuiWithMockedCommand
+      styleCtx <- widgetGetStyleContext entry
+      styleContextAddClass styleCtx "error"
+      runCtxActions ctx $ invokePreview entry entryApi "cmd->asd"
+      hasErrorClass <- styleContextHasClass styleCtx "error"
+      hasErrorClass `shouldBe` False
+
+    it "invokes preview on the command" $ do
+      (ctx, entry, entryApi, (_, previewReader, previewResetReader)) <- loadDefaultGuiWithMockedCommand
+      runCtxActions ctx $ invokePreview entry entryApi "cmd->asd"
+      previewText <- previewReader
+      previewResetText <- previewResetReader
+      previewText `shouldBe` Just "asd"
+      previewResetText `shouldBe` Nothing
+
+    it "resets preview before next preview" $ do
+      (ctx, entry, entryApi, (_, previewReader, previewResetReader)) <- loadDefaultGuiWithMockedCommand
+      runCtxActions ctx $ invokePreview entry entryApi "cmd->asd"
+      runCtxActions ctx $ invokePreview entry entryApi "cmd->as"
+      previewText <- previewReader
+      previewResetText <- previewResetReader
+      previewText `shouldBe` Just "as"
+      previewResetText `shouldBe` Just "called"
+
+    it "executes the command" $ do
+      (ctx, entry, entryApi, (executeReader, _, _)) <- loadDefaultGuiWithMockedCommand
+      runCtxActions ctx $ invokeCommand entry entryApi "cmd->asd"
+      executed <- executeReader
+      executed `shouldBe` Just "asd"
+
+    it "clears command entry when executing a command" $ do
+      (ctx, entry, entryApi, _) <- loadDefaultGuiWithMockedCommand
+      runCtxActions ctx $ invokeCommand entry entryApi "cmd->asd"
+      text <- entryGetText entry
+      text `shouldBe` ""
+
+    it "resets the last preview command before executing a command" $ do
+      (ctx, entry, entryApi, (executeReader, _, previewResetReader)) <- loadDefaultGuiWithMockedCommand
+      runCtxActions ctx $ invokeCommand entry entryApi "cmd->asd"
+      executed <- executeReader
+      previewResetText <- previewResetReader
+      executed `shouldBe` Just "asd"
+      previewResetText `shouldBe` Just "called"
+
+invokePreview :: Entry -> EntryApi -> String -> App ()
+invokePreview entry api text = do
+    liftIO $ entrySetText entry text
+    fst api
+
+invokeCommand :: Entry -> EntryApi -> String -> App ()
+invokeCommand entry api text = do
+    invokePreview entry api text
+    _ <- snd api
+    return()
+
+loadDefaultGuiWithMockedCommand :: IO (Context, Entry, EntryApi, CommandHandlerReaders)
+loadDefaultGuiWithMockedCommand = do
+    defaultCtx <- loadDefaultContext
+    (matcher, readHandledCommands) <- mockedMatcher "cmd->"
+    let ctx = defaultCtx{baseCommands = matcher}
+    entry <- entryNew
+    ret <- runApp ctx (newCommandEntryDetached entry)
+    return (ctx, entry, ret, readHandledCommands)
+
+mockedMatcher :: String -> IO (CommandMatcher, CommandHandlerReaders)
+mockedMatcher prefix = do
+    (handler, readHandledCommands) <- recordingHandler
+    let matcher = createMatcherForPrefix prefix handler
+    return (matcher, readHandledCommands)
+
+recordingHandler :: IO (String -> CommandHandler, CommandHandlerReaders)
+recordingHandler = do
+    cmd <- newIORef Nothing
+    previewCmd <- newIORef Nothing
+    previewResetCmd <- newIORef Nothing
+    return (
+                \params -> CommandHandler
+                            (Just $ PreviewCommandHandler
+                                (liftIO $ writeIORef previewCmd $ Just params)
+                                (liftIO $ writeIORef previewResetCmd $ Just "called"))
+                            (liftIO $ writeIORef cmd $ Just params),
+                (
+                    readIORef cmd,
+                    readIORef previewCmd,
+                    readIORef previewResetCmd
+                )
+            )
diff --git a/test/Hob/Ui/Editor/SearchSpec.hs b/test/Hob/Ui/Editor/SearchSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Ui/Editor/SearchSpec.hs
@@ -0,0 +1,237 @@
+module Hob.Ui.Editor.SearchSpec (main, spec) where
+
+import Control.Monad              (replicateM_)
+import Data.Maybe
+import Data.Text                  (pack)
+import Graphics.UI.Gtk
+import Graphics.UI.Gtk.SourceView (SourceView)
+import Test.Hspec
+
+import           Hob.Context
+import qualified Hob.Context.UiContext as HC
+import           Hob.Ui.Editor         (getActiveEditor, newEditorForText)
+import           Hob.Ui.Editor.Search
+
+import HobTest.Context.Default
+import HobTest.Control
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+  describeSearchPreview loadGuiAndPreviewSearch resetSearchPreview
+  describeSearchExecute loadGuiAndExecuteSearch findFirstFromCursor
+  describeSearchExecute loadGuiAndInitReplace (\editor text -> startReplace editor text "-")
+
+  describe "search for the next match" $ do
+    it "highlights the next match from cursor on execute" $ do
+      buffer <- loadEditorAndExecuteAfterSearch findNext
+      buffer `shouldHaveSelectionOffsets` (15, 19)
+
+    it "wraps the search from start if there are no matches till the end on execute" $ do
+      buffer <- loadEditorAndExecuteAfterSearch $ \editor -> replicateM_ 3 $ findNext editor
+      buffer `shouldHaveSelectionOffsets` (0, 4)
+
+    it "scrolls to the current match on execute" $ do
+      let commands editor = do
+            findFirstFromCursor editor "customised search string"
+            findNext editor
+      ensureCursorVisibleAfter commands
+
+  describe "search reset command on mode exit" $ do
+    it "removes all search tags on cleanup" $ do
+      buffer <- loadEditorAndExecuteAfterSearch resetSearch
+      tagStates <- checkSearchPreviewTagsAtRanges buffer [(0, 4), (15, 19)]
+      tagStates `shouldBe` [(False, False), (False, False)]
+
+    it "stops the next search" $ do
+      buffer <- loadEditorAndExecuteAfterSearch $ \editor -> resetSearch editor >> findNext editor
+      buffer `shouldHaveSelectionOffsets` (0, 4)
+
+  describe "search for the previous match" $ do
+    it "highlights the previous match from cursor on execute" $ do
+      buffer <- loadEditorAndExecuteAfterSearch $ \editor -> findNext editor >> findPrevious editor
+      buffer `shouldHaveSelectionOffsets` (0, 4)
+
+    it "wraps the search from start if there are no matches till the end on execute" $ do
+      buffer <- loadEditorAndExecuteAfterSearch findPrevious
+      buffer `shouldHaveSelectionOffsets` (21, 25)
+
+    it "scrolls to the current match on execute" $ do
+      let commands editor = do
+            findFirstFromCursor editor "customised search string"
+            findPrevious editor
+      ensureCursorVisibleAfter commands
+
+  describe "replace next" $ do
+    it "does not replace if there is no highlighted text" $ do
+      (ctx, buffer) <- loadGuiWithEditor
+      withActiveEditor ctx replaceNext
+      buffer `shouldHaveText` "text - initial text! text"
+
+    it "replaces previously highlighted text" $ do
+      (ctx, buffer) <- loadGuiAndInitReplace
+      withActiveEditor ctx replaceNext
+      buffer `shouldHaveText` "New:TEXT:New - initial text! text"
+
+    it "does not replace if the highlighted text doesnt match the search string" $ do
+      (ctx, buffer) <- loadGuiAndInitReplace
+      (s, _) <- textBufferGetSelectionBounds buffer
+      e <- textBufferGetIterAtOffset buffer 7
+      textBufferSelectRange buffer s e
+      withActiveEditor ctx replaceNext
+      buffer `shouldHaveText` "text - initial text! text"
+
+    it "stops the next search" $ do
+      (ctx, buffer) <- loadGuiAndInitReplace
+      withActiveEditor ctx $ \editor -> do
+          resetReplace editor
+          findNext editor
+      buffer `shouldHaveSelectionOffsets` (0, 4)
+
+    it "stops the next replace" $ do
+      (ctx, buffer) <- loadGuiAndInitReplace
+      withActiveEditor ctx $ \editor -> do
+          resetReplace editor
+          findFirstFromCursor editor "text"
+          replaceNext editor
+      buffer `shouldHaveText` "text - initial text! text"
+
+describeSearchPreview :: IO (Context, TextBuffer) -> (SourceView -> IO ()) -> Spec
+describeSearchPreview initializePreview resetFnc =
+  describe "search preview for the first match" $ do
+    it "creates search tag on preview" $ do
+      (_, buffer) <- initializePreview
+      tagTable <- textBufferGetTagTable buffer
+      tag <- textTagTableLookup tagTable "search"
+      isNothing tag `shouldBe` False
+
+    it "applies search tag for matches on preview" $ do
+      (_, buffer) <- initializePreview
+      tagStates <- checkSearchPreviewTagsAtRanges buffer [(0, 4), (15, 19)]
+      tagStates `shouldBe` [(True, True), (True, True)]
+
+    it "removes all search tags on preview reset" $ do
+      (ctx, buffer) <- initializePreview
+      withActiveEditor ctx resetFnc
+      tagStates <- checkSearchPreviewTagsAtRanges buffer [(0, 4), (15, 19)]
+      tagStates `shouldBe` [(False, False), (False, False)]
+
+describeSearchExecute :: IO (Context, TextBuffer) -> (SourceView -> String -> IO ()) -> Spec
+describeSearchExecute initializeExecute invokeFirstFnc =
+  describe "search execute for the first match" $ do
+    it "applies search tag for matches on execute" $ do
+      (_, buffer) <- initializeExecute
+      tagStates <- checkSearchPreviewTagsAtRanges buffer [(0, 4), (15, 19)]
+      tagStates `shouldBe` [(True, True), (True, True)]
+
+    it "highlights the first match on execute" $ do
+      (_, buffer) <- initializeExecute
+      buffer `shouldHaveSelectionOffsets` (0, 4)
+
+    it "highlights the next match from cursor on execute" $ do
+      (ctx, buffer) <- initializeExecute
+      withActiveEditor ctx (`invokeFirstFnc` "text")
+      buffer `shouldHaveSelectionOffsets` (15, 19)
+
+    it "wraps the search from start if there are no matches till the end on execute" $ do
+      (ctx, buffer) <- initializeExecute
+      withActiveEditor ctx $ \editor -> replicateM_ 3 $ invokeFirstFnc editor "text"
+      buffer `shouldHaveSelectionOffsets` (0, 4)
+
+    it "scrolls to the current match on execute" $
+      ensureCursorVisibleAfter $ \editor -> invokeFirstFnc editor "customised search string at the end"
+
+shouldHaveSelectionOffsets :: TextBuffer -> (Int, Int) -> IO ()
+shouldHaveSelectionOffsets buffer offsets = do
+    (start, end) <- getSelectionOffsets buffer
+    (start, end) `shouldBe` offsets
+
+shouldHaveText :: TextBuffer -> String -> IO ()
+shouldHaveText buffer text = do
+    bufferText <- (buffer `get` textBufferText)::IO String
+    bufferText `shouldBe` text
+
+loadEditorAndExecuteAfterSearch :: (SourceView -> IO ()) -> IO TextBuffer
+loadEditorAndExecuteAfterSearch actions = do
+    (ctx, buffer) <- loadGuiAndExecuteSearch
+    withActiveEditor ctx actions
+    return buffer
+
+
+ensureCursorVisibleAfter :: (SourceView -> IO()) -> IO ()
+ensureCursorVisibleAfter commands = do
+    ctx <- loadDefaultContext
+    let notebook = HC.mainNotebook . uiContext $ ctx
+    let editorText = (concat . replicate 1000  $ "text - initial text! \n") ++ "customised search string at the end\n"
+    runCtxActions ctx $ newEditorForText notebook Nothing $ pack editorText
+    processGtkEvents
+    withActiveEditor ctx commands
+    processGtkEvents
+    withActiveEditor ctx $ \editor -> do
+        buffer <- textViewGetBuffer editor
+        visible <- textViewGetVisibleRect editor
+        caretIter <- textBufferGetIterAtMark buffer =<< textBufferGetInsert buffer
+        cursor <- textViewGetIterLocation editor caretIter
+        isRectangleInside visible cursor `shouldBe` True
+
+loadGuiAndPreviewSearch :: IO (Context, TextBuffer)
+loadGuiAndPreviewSearch = do
+    ctx <- loadDefaultContext
+    let notebook = HC.mainNotebook . uiContext $ ctx
+    runCtxActions ctx $ newEditorForText notebook Nothing $ pack "text - initial text! text"
+    Just editor <- getActiveEditor ctx
+    highlightSearchPreview editor "text"
+    buffer <- textViewGetBuffer editor
+    return (ctx, buffer)
+
+loadGuiAndExecuteSearch :: IO (Context, TextBuffer)
+loadGuiAndExecuteSearch = loadGuiAndExecute $ \editor -> findFirstFromCursor editor "text"
+
+loadGuiAndInitReplace:: IO (Context, TextBuffer)
+loadGuiAndInitReplace = loadGuiAndExecute $ \editor -> startReplace editor "text" "New:TEXT:New"
+
+loadGuiWithEditor :: IO (Context, TextBuffer)
+loadGuiWithEditor = loadGuiAndExecute $ const $ return ()
+
+loadGuiAndExecute :: (SourceView -> IO()) -> IO (Context, TextBuffer)
+loadGuiAndExecute actions = do
+    (ctx, buffer) <- loadGuiAndPreviewSearch
+    withActiveEditor ctx resetSearchPreview
+    iterBufferStart <- textBufferGetIterAtOffset buffer 0
+    textBufferSelectRange buffer iterBufferStart iterBufferStart
+    withActiveEditor ctx actions
+    return (ctx, buffer)
+
+getSelectionOffsets :: TextBuffer -> IO (Int, Int)
+getSelectionOffsets buffer = do
+    (iterStart, iterEnd) <- textBufferGetSelectionBounds buffer
+    start <- textIterGetOffset iterStart
+    end <- textIterGetOffset iterEnd
+    return (start, end)
+
+checkSearchPreviewTagsAtRanges :: TextBuffer -> [(Int, Int)] -> IO [(Bool, Bool)]
+checkSearchPreviewTagsAtRanges buffer ranges = do
+      tagTable <- textBufferGetTagTable buffer
+      tag <- textTagTableLookup tagTable "search"
+      mapM (checkPair tag) ranges
+      where checkPair tag (l, r) = do
+                iterL <- textBufferGetIterAtOffset buffer l
+                iterR <- textBufferGetIterAtOffset buffer r
+                checkL <- textIterBeginsTag iterL tag
+                checkR <- textIterEndsTag iterR tag
+                return (checkL, checkR)
+
+isRectangleInside :: Rectangle -> Rectangle -> Bool
+isRectangleInside (Rectangle ax ay aw ah) (Rectangle bx by bw bh) =
+    (ax <= bx) && (ay <= by) && (ax+aw >= bx+bw) && (ay+ah >= by+bh)
+
+processGtkEvents :: IO ()
+processGtkEvents = replicateM_ 500 $ mainIterationDo False
+
+withActiveEditor :: Context -> (SourceView -> IO b) -> IO b
+withActiveEditor ctx actions = do
+    Just editor <- getActiveEditor ctx
+    actions editor
+
diff --git a/test/Hob/Ui/EditorSpec.hs b/test/Hob/Ui/EditorSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Ui/EditorSpec.hs
@@ -0,0 +1,123 @@
+module Hob.Ui.EditorSpec (main, spec) where
+
+import Test.Hspec
+
+import Control.Monad.Reader
+import Data.IORef
+import Data.Maybe
+import Data.Monoid
+import Data.Text                  (pack, unpack)
+import Graphics.UI.Gtk
+import Graphics.UI.Gtk.SourceView (SourceView, castToSourceBuffer,
+                                   sourceBufferUndo)
+
+import           Hob.Context
+import qualified Hob.Context.UiContext as HC
+import           Hob.Ui
+import           Hob.Ui.Editor
+
+import HobTest.Context.Default
+import HobTest.Control
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "editor ui" $ do
+    it "does not allow to undo the intial loaded source" $ do
+      ctx <- loadDefaultContext
+      editor <- launchEditorTab ctx $ Just "/xxx/testName.hs"
+      buffer <- textViewGetBuffer editor
+      sourceBufferUndo $ castToSourceBuffer buffer
+      editorText <- getEditorText editor
+      unpack editorText `shouldBe` "initial text"
+
+    it "sets the tab title when opening a file" $ do
+      ctx <- loadDefaultContext
+      _ <- launchEditorTab ctx $ Just "/xxx/testName.hs"
+      tabText <- getActiveEditorTabText ctx
+      tabText `shouldBe` "testName.hs"
+
+    it "updates the tab title to reflect if buffer is modified" $ do
+      ctx <- launchNewFileAndSetModified
+      tabText <- getActiveEditorTabText ctx
+      tabText `shouldBe` "testName.hs*"
+
+    it "retrieves active editor" $ do
+      ctx <- loadDefaultContext
+      editor <- launchEditorTab ctx $ Just "/xxx/testName.hs"
+      activeEditor <- getActiveEditor ctx
+      fromJust activeEditor == editor `shouldBe` True
+
+    it "registers the new editor in the context on creation" $ do
+      ctx <- loadDefaultContext
+      _ <- launchEditorTab ctx $ Just "/xxx/testName.hs"
+      registeredEditors <- getEditors $ editors ctx
+      length registeredEditors `shouldBe` 1
+
+    it "retrieves editor text" $ do
+      ctx <- loadDefaultContext
+      editor <- launchEditorTab ctx $ Just "/xxx/testName.hs"
+      text <- getEditorText editor
+      text `shouldBe` pack "initial text"
+
+    it "retrieves active editor text" $ do
+      ctx <- loadDefaultContext
+      _ <- launchEditorTab ctx $ Just "/xxx/testName.hs"
+      text <- getActiveEditorText ctx
+      fromJust text `shouldBe` pack "initial text"
+
+    it "retrieves Nothing for active editor text if there is no active editor" $ do
+      ctx <- loadDefaultContext
+      text <- getActiveEditorText ctx
+      text `shouldBe` Nothing
+
+    it "retrieves current editor from notebook" $ do
+      ctx <- loadDefaultContext
+      editor <- launchEditorTab ctx $ Just "/xxx/testName.hs"
+      tab <- getActiveEditorTab ctx
+      activeEditor <- getEditorFromNotebookTab $ fromJust tab
+      fromJust activeEditor == editor `shouldBe` True
+
+    it "retrieves the set filepath for an editor" $ do
+      ctx <- loadDefaultContext
+      editor <- launchEditorTab ctx $ Just "/xxx/testName.hs"
+      filePath1 <- getEditorFilePath editor
+      setEditorFilePath editor $ Just "/tmp/xxx"
+      filePath2 <- getEditorFilePath editor
+      filePath1 `shouldBe` Just "/xxx/testName.hs"
+      filePath2 `shouldBe` Just "/tmp/xxx"
+
+
+    it "invokes mode teardown on exitMode" $ do
+      record <- newIORef False
+      ctx <- loadDefaultContext
+      editor <- launchEditorTab ctx $ Just "/xxx/testName.hs"
+      widgetGrabFocus editor
+      runCtxActions ctx $ enterMode $ Mode "testmode" mempty (liftIO $ writeIORef record True)
+      runCtxActions ctx exitLastMode
+      cleanupInvoked <- readIORef record
+      cleanupInvoked `shouldBe` True
+
+launchNewFileAndSetModified :: IO Context
+launchNewFileAndSetModified = do
+    ctx <- loadDefaultContext
+    _ <- launchEditorTab ctx $ Just "/xxx/testName.hs"
+    buffer <- textViewGetBuffer . fromJust =<< getActiveEditor ctx
+    textBufferSetModified buffer True
+    return ctx
+
+launchEditorTab :: Context -> Maybe FilePath -> IO SourceView
+launchEditorTab ctx file = do
+    let notebook = HC.mainNotebook . uiContext $ ctx
+    runCtxActions ctx $ newEditorForText notebook file $ pack "initial text"
+    mEditor <- getActiveEditor ctx
+    return $ fromJust mEditor
+
+getActiveEditorTabText :: Context  -> IO String
+getActiveEditorTabText ctx = do
+    let notebook = HC.mainNotebook . uiContext $ ctx
+    currentlyActiveEditor <- getActiveEditorTab ctx
+    text <- notebookGetTabLabelText notebook $ fromJust currentlyActiveEditor
+    return $ fromJust text
diff --git a/test/Hob/Ui/SidebarSearchSpec.hs b/test/Hob/Ui/SidebarSearchSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Ui/SidebarSearchSpec.hs
@@ -0,0 +1,150 @@
+module Hob.Ui.SidebarSearchSpec (main, spec) where
+
+import Data.Tree
+import Graphics.UI.Gtk
+
+import qualified Hob.Context              as HC
+import qualified Hob.Context.FileContext  as HFC
+import qualified Hob.Context.StyleContext as HSC
+import qualified Hob.Context.UiContext    as HC
+import           Hob.DirectoryTree
+import           Hob.Ui
+import           Hob.Ui.SidebarSearch
+
+import Test.Hspec
+
+import HobTest.Context.Default
+import HobTest.Sidebar
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "sidebar search" $ do
+    it "shows the named search box" $ do
+      ctx <- sideBarSearchContext
+      startSidebarSearch ctx ""
+      name <- widgetGetName $ HC.sidebarTreeSearch . HC.uiContext $ ctx
+      name `shouldBe` "sidebarSearchEntry"
+
+    it "places the cursor on the first match on the search start" $ do
+      ctx <- sideBarSearchContext
+      cursorShouldBeOnAfterSearch ctx "greenFile" [1]
+
+    it "finds part even if start can be eaten" $ do
+      ctx <- sideBarSearchContext
+      cursorShouldBeOnAfterSearch ctx "greenFileZ" [6]
+
+    it "places the cursor on the first nested match on the search start" $ do
+      ctx <- sideBarSearchContext
+      cursorShouldBeOnAfterSearch ctx "redFile" [0, 0]
+
+    it "places the cursor on the first match on the search start even if started from the middle" $ do
+      ctx <- sideBarSearchContext
+      treeViewSetCursor (HC.sidebarTree . HC.uiContext $ ctx) [1] Nothing
+      cursorShouldBeOnAfterSearch ctx "redFile" [0, 0]
+
+    it "places the cursor on the first match again on the search start even if had the same search text before" $ do
+      ctx <- sideBarSearchContext
+      cursorShouldBeOnAfterSearch ctx "redFile" [0, 0]
+      treeViewSetCursor (HC.sidebarTree . HC.uiContext $ ctx) [1] Nothing
+      cursorShouldBeOnAfterSearch ctx "redFile" [0, 0]
+
+    it "only looks for the leaf nodes" $ do
+      ctx <- sideBarSearchContext
+      cursorShouldBeOnAfterSearch ctx "red" [0, 0]
+
+    it "uses fuzzy matching on file name" $ do
+      ctx <- sideBarSearchContext
+      cursorShouldBeOnAfterSearch ctx "r d e" [0, 0]
+
+    it "uses fuzzy matching on path" $ do
+      ctx <- sideBarSearchContext
+      cursorShouldBeOnAfterSearch ctx "r dD r r d e" [0, 0]
+
+    it "allows path separator while fuzzy matching on path" $ do
+      ctx <- sideBarSearchContext
+      cursorShouldBeOnAfterSearch ctx "r/red e" [0, 0]
+
+    it "stays on the same path when search is refined" $ do
+      ctx <- sideBarSearchContext
+      cursorShouldBeOnAfterSearch ctx "r/r d" [0, 0]
+      cursorShouldBeOnAfterRefine ctx "r/r d e" [0, 0]
+
+    it "moves to the new match when search is refined" $ do
+      ctx <- sideBarSearchContext
+      cursorShouldBeOnAfterSearch ctx "r/r d" [0, 0]
+      cursorShouldBeOnAfterRefine ctx "r/r d e2" [0, 1]
+
+    it "focuses next file match when requested" $ do
+      ctx <- sideBarSearchContext
+      cursorShouldBeOnAfterSearchAndContinue ctx "r d e" [[0, 0], [0, 1]]
+
+    it "focuses next path match when requested" $ do
+      ctx <- sideBarSearchContext
+      cursorShouldBeOnAfterSearchAndContinue ctx "r/r d e" [[0, 0], [0, 1]]
+
+    it "focuses next multi-level path match when requested" $ do
+      ctx <- sideBarSearchContext
+      cursorShouldBeOnAfterSearchAndContinue ctx "/ Dir//r d e" [[3, 0, 0, 0], [4, 1, 0, 0]]
+
+    it "collapses previously matched path after searching in next subtree" $ do
+      ctx <- sideBarSearchContext
+      startSidebarSearch ctx "/Dir//r d e"
+      idx <- HC.runApp ctx initFileTreeIndex
+      HC.runApp ctx $ continueSidebarSearch idx
+      expanded <- treeViewRowExpanded (HC.sidebarTree . HC.uiContext $ ctx) [3]
+      expanded `shouldBe` False
+
+    it "allows case insensitive search" $ do
+      ctx <- sideBarSearchContext
+      cursorShouldBeOnAfterSearchAndContinue ctx "/ DIR//R D E" [[3, 0, 0, 0], [4, 1, 0, 0]]
+
+    it "finds next top-level file" $ do
+      ctx <- sideBarSearchContext
+      cursorShouldBeOnAfterSearchAndContinue ctx "green file" [[1], [3,0,0,0], [5]]
+
+    it "applies error style class if there are no matches" $ do
+      ctx <- sideBarSearchContext
+      startSidebarSearch ctx "AAA!!!AAA"
+      ctx `sidebarTreeSearchErrorStateShouldBe` True
+
+    it "removes error style on empty search text" $ do
+      ctx <- sideBarSearchContext
+      startSidebarSearch ctx "AAA!!!AAA"
+      entrySetText (HC.sidebarTreeSearch . HC.uiContext $ ctx) ""
+      ctx `sidebarTreeSearchErrorStateShouldBe` False
+
+    it "removes error style on a match" $ do
+      ctx <- sideBarSearchContext
+      startSidebarSearch ctx "AAA!!!AAA"
+      entrySetText (HC.sidebarTreeSearch . HC.uiContext $ ctx) "red"
+      ctx `sidebarTreeSearchErrorStateShouldBe` False
+
+sideBarSearchFileTreeStub :: IO (Forest DirectoryTreeElement)
+sideBarSearchFileTreeStub = return [
+    Node (DirectoryTreeElement "redDir" "/xxx/redDir" True) [
+        Node (DirectoryTreeElement "redFile1" "/xxx/redDir/redFile1" False) [],
+        Node (DirectoryTreeElement "redFile2" "/xxx/redDir/redFile2" False) []],
+    Node (DirectoryTreeElement "greenFile" "/xxx/greenFile" False) [],
+    Node (DirectoryTreeElement "redFile" "/xxx/redFile" False) [],
+    Node (DirectoryTreeElement "greenDir" "/xxx/greenDir" True) [
+        Node (DirectoryTreeElement "greenDir" "/xxx/greenDir/greenDir" True) [
+            Node (DirectoryTreeElement "greenDir" "/xxx/greenDir/greenDir/greenDir" True) [
+                Node (DirectoryTreeElement "redFile" "/xxx/greenDir/greenDir/greenDir/redFile" False) []]]],
+    Node (DirectoryTreeElement "blueFolder" "/xxx/blueFolder" True) [
+        Node (DirectoryTreeElement "blueFolder" "/xxx/blueFolder/blueFolder" True) [
+            Node (DirectoryTreeElement "blueFolder" "/xxx/blueFolder/blueFolder/blueFolder" True) [
+                Node (DirectoryTreeElement "redFile" "/xxx/blueFolder/blueFolder/blueFolder/redFile" False) []]],
+        Node (DirectoryTreeElement "blueDir" "/xxx/blueFolder/blueDir" True) [
+            Node (DirectoryTreeElement "blueDir" "/xxx/blueFolder/blueDir/blueDir" True) [
+                Node (DirectoryTreeElement "redFile" "/xxx/blueFolder/blueDir/blueDir/redFile" False) []]]],
+    Node (DirectoryTreeElement "greenFile2" "/xxx/greenFile2" False) [],
+    Node (DirectoryTreeElement "greegreenFileZ" "/xxx/greegreenFileZ" False) []]
+
+sideBarSearchContext :: IO HC.Context
+sideBarSearchContext = do
+    sc <- HSC.defaultStyleContext "app-data"
+    fc <- HFC.defaultFileContext emptyFileLoader blackholeFileWriter sideBarSearchFileTreeStub
+    loadGui fc sc
diff --git a/test/Hob/Ui/SidebarSpec.hs b/test/Hob/Ui/SidebarSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/Ui/SidebarSpec.hs
@@ -0,0 +1,61 @@
+module Hob.Ui.SidebarSpec (main, spec) where
+
+import Data.Maybe
+import Data.Text       (unpack)
+import Graphics.UI.Gtk
+
+import qualified Hob.Context           as HC
+import qualified Hob.Context.UiContext as HC
+import           Hob.Control
+import           Hob.Ui.Editor
+
+import Test.Hspec
+
+import HobTest.Context.Default
+import HobTest.Context.Stubbed
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "sidebar" $ do
+    it "is named" $ do
+      ctx <- loadDefaultContext
+      name <- widgetGetName $ HC.sidebarTree . HC.uiContext $ ctx
+      name `shouldBe` "directoryListing"
+
+    it "opens a file editor" $ do
+      ctx <- loadStubbedContext
+      activateDirectoryPath ctx [2]
+      flushEvents
+      editorText <- getActiveEditorText ctx
+      (unpack . fromJust $ editorText) `shouldBe` "file contents for /xxx/c"
+
+    it "does not open a file editor for directory" $ do
+      ctx <- loadStubbedContext
+      activateDirectoryPath ctx [0]
+      pagesAfterActivatingDirectory <- getNumberOfEditorPages ctx
+      pagesAfterActivatingDirectory `shouldBe` 0
+
+    it "expands subtree on directory activation" $ do
+      ctx <- loadStubbedContext
+      activateDirectoryPath ctx [0]
+      expanded <- treeViewRowExpanded (HC.sidebarTree . HC.uiContext $ ctx) [0]
+      expanded `shouldBe` True
+
+    it "does not open a file editor for files it cannot read" $ do
+      ctx <- loadStubbedContext
+      activateDirectoryPath ctx [3]
+      pagesAfterActivatingDirectory <- getNumberOfEditorPages ctx
+      pagesAfterActivatingDirectory `shouldBe` 0
+
+activateDirectoryPath :: HC.Context -> TreePath -> IO ()
+activateDirectoryPath ctx path = do
+    let treeView = HC.sidebarTree . HC.uiContext $ ctx
+    firstColumn <- treeViewGetColumn treeView 0
+    treeViewRowActivated treeView path $ fromJust firstColumn
+
+
+getNumberOfEditorPages :: HC.Context -> IO Int
+getNumberOfEditorPages = notebookGetNPages . HC.mainNotebook . HC.uiContext
diff --git a/test/Hob/UiSpec.hs b/test/Hob/UiSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Hob/UiSpec.hs
@@ -0,0 +1,21 @@
+module Hob.UiSpec (main, spec) where
+
+import Graphics.UI.Gtk
+
+import qualified Hob.Context           as HC
+import qualified Hob.Context.UiContext as HC
+
+import Test.Hspec
+
+import HobTest.Context.Default
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec =
+  describe "mainWindow" $
+    it "is named" $ do
+      ctx <- loadDefaultContext
+      name <- widgetGetName $ HC.mainWindow . HC.uiContext $ ctx
+      name `shouldBe` "mainWindow"
diff --git a/test/HobTest/Context/Default.hs b/test/HobTest/Context/Default.hs
new file mode 100644
--- /dev/null
+++ b/test/HobTest/Context/Default.hs
@@ -0,0 +1,32 @@
+module HobTest.Context.Default (
+        loadDefaultContext,
+        emptyFileTree,
+        emptyFileLoader,
+        blackholeFileWriter
+        ) where
+
+import Data.Text (pack)
+
+import qualified Hob.Context              as HC
+import qualified Hob.Context.FileContext  as HFC
+import qualified Hob.Context.StyleContext as HSC
+import           Hob.Control              (flushEvents)
+
+import Hob.Ui
+
+blackholeFileWriter :: HFC.FileWriter
+blackholeFileWriter _ _ = return ()
+
+emptyFileTree :: HFC.FileTreeLoader
+emptyFileTree = return []
+
+emptyFileLoader :: HFC.FileLoader
+emptyFileLoader _ = return $ Just $ pack ""
+
+loadDefaultContext :: IO HC.Context
+loadDefaultContext = do
+    sc <- HSC.defaultStyleContext "app-data"
+    fc <- HFC.defaultFileContext emptyFileLoader blackholeFileWriter emptyFileTree
+    ctx <- loadGui fc sc
+    flushEvents
+    return ctx
diff --git a/test/HobTest/Context/Stubbed.hs b/test/HobTest/Context/Stubbed.hs
new file mode 100644
--- /dev/null
+++ b/test/HobTest/Context/Stubbed.hs
@@ -0,0 +1,41 @@
+module HobTest.Context.Stubbed (
+        loadStubbedContext,
+        stubbedFileLoader,
+        failingFileWriter
+        ) where
+
+import Control.Monad.Error (throwError)
+import Data.Text           (pack)
+import Data.Tree
+
+import qualified Hob.Context              as HC
+import qualified Hob.Context.FileContext  as HFC
+import qualified Hob.Context.StyleContext as HSC
+
+import Hob.DirectoryTree
+import Hob.Ui
+
+fileTreeStub :: IO (Forest DirectoryTreeElement)
+fileTreeStub = return [
+    Node (DirectoryTreeElement "a" "/xxx/a" True) [
+        Node (DirectoryTreeElement "b" "/xxx/a/b" False) []],
+    Node (DirectoryTreeElement "a.b" "/xxx/a.b" False) [],
+    Node (DirectoryTreeElement "c" "/xxx/c" False) [],
+    Node (DirectoryTreeElement "-" "/xxx/cannotRead" False) []]
+
+failingFileWriter :: HFC.FileWriter
+failingFileWriter _ _ = throwError $ userError "cannot write files stub"
+
+stubbedFileLoader :: HFC.FileLoader
+stubbedFileLoader "/xxx/c" = return $ Just $ pack "file contents for /xxx/c"
+stubbedFileLoader "/xxx/a/b" = return $ Just $ pack "file contents for /xxx/a/b"
+stubbedFileLoader "/xxx/a.b" = return $ Just $ pack "file contents for /xxx/a.b"
+stubbedFileLoader "/xxx/cannotRead" = return Nothing
+stubbedFileLoader "/xxx/testName.hs" = return $ Just $ pack "file contents for /xxx/testName.hs"
+stubbedFileLoader path = throwError $ userError $ "cannot open unknown file: "++path
+
+loadStubbedContext :: IO HC.Context
+loadStubbedContext = do
+    sc <- HSC.defaultStyleContext "app-data"
+    fc <- HFC.defaultFileContext stubbedFileLoader failingFileWriter fileTreeStub
+    loadGui fc sc
diff --git a/test/HobTest/Control.hs b/test/HobTest/Control.hs
new file mode 100644
--- /dev/null
+++ b/test/HobTest/Control.hs
@@ -0,0 +1,11 @@
+module HobTest.Control (
+    runCtxActions
+) where
+
+import Hob.Context
+import Hob.Control (flushEvents)
+
+runCtxActions :: Context -> App() -> IO()
+runCtxActions ctx actions = do
+    deferredRunner ctx actions
+    flushEvents
diff --git a/test/HobTest/Editor.hs b/test/HobTest/Editor.hs
new file mode 100644
--- /dev/null
+++ b/test/HobTest/Editor.hs
@@ -0,0 +1,29 @@
+module HobTest.Editor (
+    launchNewFile,
+    launchEditorTab,
+    getNumberOfEditorPages
+    ) where
+
+import Graphics.UI.Gtk
+
+import Hob.Command.NewTab
+import Hob.Context
+import Hob.Context.UiContext
+
+import HobTest.Context.Default
+import HobTest.Control
+
+launchNewFile :: IO Context
+launchNewFile = do
+    ctx <- loadDefaultContext
+    runCtxActions ctx $ commandExecute editNewFileCommandHandler
+    return ctx
+
+launchEditorTab :: Context -> String -> IO ()
+launchEditorTab ctx file = do
+    let notebook = mainNotebook . uiContext $ ctx
+    launchNewFileEditor ctx notebook file
+
+getNumberOfEditorPages :: Context -> IO Int
+getNumberOfEditorPages = notebookGetNPages . mainNotebook . uiContext
+
diff --git a/test/HobTest/Sidebar.hs b/test/HobTest/Sidebar.hs
new file mode 100644
--- /dev/null
+++ b/test/HobTest/Sidebar.hs
@@ -0,0 +1,56 @@
+module HobTest.Sidebar (
+    sidebarTreeSearchErrorStateShouldBe,
+    cursorShouldBeOnAfterSearch,
+    cursorShouldBeOnAfterRefine,
+    cursorShouldBeOnAfterSearchAndContinue,
+    cursorShouldBeOn,
+    ) where
+
+import Data.Maybe
+import Graphics.UI.Gtk
+import Graphics.UI.Gtk.General.StyleContext
+
+import qualified Hob.Context           as HC
+import qualified Hob.Context.UiContext as HC
+import           Hob.Ui.SidebarSearch
+
+import Test.Hspec
+
+
+sidebarTreeSearchErrorStateShouldBe :: HC.Context -> Bool -> IO ()
+sidebarTreeSearchErrorStateShouldBe ctx state = do
+      styleContext <- widgetGetStyleContext $ HC.sidebarTreeSearch . HC.uiContext $ ctx
+      hasErrorClass <- styleContextHasClass styleContext "error"
+      hasErrorClass `shouldBe` state
+
+cursorShouldBeOnAfterSearch :: HC.Context -> String -> TreePath -> IO ()
+cursorShouldBeOnAfterSearch ctx search expectedPath = do
+      startSidebarSearch ctx search
+      ctx `cursorShouldBeOn` expectedPath
+
+cursorShouldBeOnAfterRefine :: HC.Context -> String -> TreePath -> IO ()
+cursorShouldBeOnAfterRefine ctx search expectedPath = do
+      entrySetText (HC.sidebarTreeSearch . HC.uiContext $ ctx) search
+      ctx `cursorShouldBeOn` expectedPath
+
+cursorShouldBeOnAfterSearchAndContinue :: HC.Context -> String -> [TreePath] -> IO ()
+cursorShouldBeOnAfterSearchAndContinue ctx search expectedPaths = do
+      startSidebarSearch ctx search
+      ctx `cursorShouldBeOn` head expectedPaths
+      mapM_ (\path -> do
+              idx <- HC.runApp ctx initFileTreeIndex
+              HC.runApp ctx $ continueSidebarSearch idx
+              ctx `cursorShouldBeOn` path)
+            $ tail expectedPaths
+      mapM_ (\path -> do
+              idx <- HC.runApp ctx initFileTreeIndex
+              HC.runApp ctx $ continueSidebarSearchBackwards idx
+              ctx `cursorShouldBeOn` path)
+            $ reverse $ init expectedPaths
+
+cursorShouldBeOn :: HC.Context -> TreePath -> IO ()
+cursorShouldBeOn ctx expectedPath = do
+    let sideBar = HC.sidebarTree . HC.uiContext $ ctx
+    (path, column) <- treeViewGetCursor sideBar
+    path `shouldBe` expectedPath
+    isNothing column `shouldBe` True
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,2 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+
