--- /dev/null
+build/
+Makefile*
+minitube.pro.user
+.settings/
+.DS_Store
+.cproject
+.project
--- /dev/null
+0.3 - June 15, 2009
+- Can sort videos by relevance, date and popularity
+- Doubleclick on video goes full screen
+- Video context menu
+- Can remove videos using the Backspace key, Mac laptops lack a Delete key
+- Keyboard shortcut to give focus to the search box
+- Load thumbnails asynchronously
+- Fixed wrong (absurdly high) number views on some videos
+- Now Minitube is ready to be translated. Italian, Russian and Portuguese translations available
+- Cosmetics
+
+0.2.1 - June 1, 2009
+- Fixed showstopper bug on Linux: Minitube fails to automatically play the next video
+
+0.2 - May 29, 2009
+- Faster playlist results
+- Ability to (re)move selected playlist items
+- Drag'n'drop playlist items
+- Uses less memory
+- Basic fullscreen mode now works
+- Show the total number of views of a video
+- Video duration is now overlayed on the thumb
+- Update notifier
+
+0.1 - May 15, 2009
+First release
--- /dev/null
+* Prerequisites
+
+To compile Minitube you need Qt4 installed.
+
+On a Debian or Ubuntu system just type:
+sudo apt-get install build-essential qt4-dev-tools
+
+On Windows and Mac get Qt4 from:
+http://trolltech.com/developer/downloads/qt/
+
+* Compiling
+Compilig on Linux is fairly easy. Just run:
+$ qmake
+and then
+$ make
+
+* Running
+./build/target/minitube
+
+
+Legal Stuff
+
+Copyright (C) 2009 Flavio Tordini
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General 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/>.
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleName</key>
+ <string>Minitube</string>
+ <key>CFBundleIconFile</key>
+ <string>minitube.icns</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleGetInfoString</key>
+ <string>Copyright 2009 Flavio Tordini</string>
+ <key>CFBundleExecutable</key>
+ <string>minitube</string>
+ <key>CFBundleIdentifier</key>
+ <string>org.tordini.flavio.minitube</string>
+</dict>
+</plist>
--- /dev/null
+ 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>.
--- /dev/null
+* Bugs
+- Item positions when moving two items down
+- Always remember the playlist width
+- Investigate ways to ensure Minitube never ever stops playing
+- Truncate overflowing text in the playlist
+- Crash occurring on stop/seach/stop/search
+- Hide the mouse cursor in full screen mode
+
+* Features
+- Build system PREFIX thing
+- Show buffering progress and title of the video
+- Windows build
+- Clear recent keywords
+- Video download
+- Show current video position and total/remaining time near the seekbar
+- Playing/Paused icon overlay on the current video thumb
+- Mark playlist items that have errors
+- Settings: number of "Recent Keywords", Phonon settings
+- Accept YouTube URLs in the search box
+- Search autocomplete
+- Dragndrop on video widget
+- Controls in Fullscreen mode
+- Show more thumbs on hover
+- Played state for playlist items
+- YouTube video details
+- YouTube related videos
+- Subtitles, see http://google2srt.sourceforge.net/
+- Saved playlists
+- Investigate fetchMore() and canFetchMore() in the Model/View API
+
+* Phonon bugs
+- Mac playback sometimes does not start
+- Phonon freezes the GUI on Mac
+- Seek does not work on Linux
--- /dev/null
+#!/bin/bash
+#
+# This script was written to get some data on how far the various translations are
+# compared to each other
+#
+# This script is donated to the public domain
+#
+# Klaas van Gend, 2008
+
+printf "\n translation file %%ready (unfinished/(total-obsolete))\n"
+printf '=============================================================\n'
+for I in `ls -1 *.ts`;
+do
+ UNFINISHED=`grep 'type="unfinished"' $I | wc -l`;
+ OBSOLETE=`grep 'obsolete' $I | wc -l`;
+ MSGLINES=`grep '</message>' $I | wc -l`;
+ let "REALLINES=$MSGLINES-$OBSOLETE";
+ let "PERCENT=(100*$UNFINISHED)/$REALLINES";
+ let "FINISHED=100-$PERCENT";
+ printf "% 18s : % 4d%% %d/(%d-%d)\n" $I $FINISHED $UNFINISHED $MSGLINES $OBSOLETE ;
+done
+printf "\n"
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="it_IT" sourcelanguage="en_US">
+<defaultcodec>UTF-8</defaultcodec>
+<context>
+ <name>AboutView</name>
+ <message>
+ <location filename="../src/AboutView.cpp" line="20"/>
+ <source>There's life outside the browser!</source>
+ <translation>C'è vita fuori del browser!</translation>
+ </message>
+ <message>
+ <location filename="../src/AboutView.cpp" line="21"/>
+ <source>Version %1</source>
+ <translation>Versione %1</translation>
+ </message>
+ <message>
+ <location filename="../src/AboutView.cpp" line="24"/>
+ <source>This is a "Technology Preview" release, do not expect it to be perfect.</source>
+ <translation>Questa è una versione Beta, non aspettarti che sia perfetta.</translation>
+ </message>
+ <message>
+ <location filename="../src/AboutView.cpp" line="25"/>
+ <source>Report bugs and send in your ideas to %1</source>
+ <translation>Segnala problemi e manda le tue idee a %1</translation>
+ </message>
+ <message>
+ <location filename="../src/AboutView.cpp" line="28"/>
+ <source>%1 is Free Software but its development takes precious time.</source>
+ <translation>%1 è Software Libero ma il suo sviluppo richiede tempo prezioso.</translation>
+ </message>
+ <message>
+ <location filename="../src/AboutView.cpp" line="29"/>
+ <source>Please <a href='%1'>donate via PayPal</a> to support the continued development of %2.</source>
+ <translation>Per favore <a href='%1'>fai una donazione con PayPal</a> per aiutare lo sviluppo di %2.</translation>
+ </message>
+ <message>
+ <location filename="../src/AboutView.cpp" line="32"/>
+ <source>Icon designed by %1.</source>
+ <translation>Icona disegnata da %1.</translation>
+ </message>
+ <message>
+ <location filename="../src/AboutView.cpp" line="33"/>
+ <source>Released under the <a href='%1'>GNU General Public License</a></source>
+ <translation>Rilasciato sotto licenza <a href="%1">GNU General Public License</a></translation>
+ </message>
+ <message>
+ <location filename="../src/AboutView.h" line="18"/>
+ <source>About</source>
+ <translation>Informazioni</translation>
+ </message>
+ <message>
+ <location filename="../src/AboutView.h" line="20"/>
+ <source>What you always wanted to know about %1 and never dared to ask</source>
+ <translation>Quello che hai sempre voluto sapere su %1 e non hai mai osato chiedere</translation>
+ </message>
+</context>
+<context>
+ <name>ClearButton</name>
+ <message>
+ <location filename="../src/searchlineedit.cpp" line="50"/>
+ <source>Clear</source>
+ <translation>Cancella</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="89"/>
+ <source>&Back</source>
+ <translation>&Indietro</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="91"/>
+ <source>Alt+Left</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="92"/>
+ <source>Go to the previous view</source>
+ <translation>Vai alla vista precedente</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="96"/>
+ <source>&Stop</source>
+ <translation>&Ferma</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="97"/>
+ <source>Stop playback and go back to the search view</source>
+ <translation>Ferma il video e torna alla ricerca</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="98"/>
+ <location filename="../src/MainWindow.cpp" line="457"/>
+ <location filename="../src/MainWindow.cpp" line="461"/>
+ <source>Esc</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="102"/>
+ <source>S&kip</source>
+ <translation>&Salta</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="103"/>
+ <source>Skip to the next video</source>
+ <translation>Salta al prossimo video</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="104"/>
+ <source>Ctrl+Right</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="109"/>
+ <location filename="../src/MainWindow.cpp" line="418"/>
+ <source>&Pause</source>
+ <translation>&Pausa</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="110"/>
+ <location filename="../src/MainWindow.cpp" line="419"/>
+ <source>Pause playback</source>
+ <translation>Metti in pausa</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="111"/>
+ <source>Space</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="116"/>
+ <location filename="../src/MainWindow.cpp" line="456"/>
+ <source>&Full Screen</source>
+ <translation>&Schermo intero</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="117"/>
+ <source>Go full screen</source>
+ <translation>Vai in modalità schermo intero</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="118"/>
+ <location filename="../src/MainWindow.cpp" line="455"/>
+ <source>Alt+Return</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="132"/>
+ <source>&YouTube</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="133"/>
+ <source>Open the YouTube video page</source>
+ <translation>Apri la pagina del video su YouTube</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="134"/>
+ <source>Ctrl+Y</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="139"/>
+ <source>&Remove</source>
+ <translation>&Elimina</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="140"/>
+ <source>Remove the selected videos from the playlist</source>
+ <translation>Elimina i video selezionati dalla playlist</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="148"/>
+ <source>Move &Up</source>
+ <translation>Sposta &sopra</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="149"/>
+ <source>Move up the selected videos in the playlist</source>
+ <translation>Sposta video selezionati verso l'alto</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="150"/>
+ <source>Ctrl+Up</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="155"/>
+ <source>Move &Down</source>
+ <translation>Sposta so&tto</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="156"/>
+ <source>Move down the selected videos in the playlist</source>
+ <translation>Sposta i video selezionati verso il basso</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="157"/>
+ <source>Ctrl+Down</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="162"/>
+ <source>&Quit</source>
+ <translation>&Esci</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="163"/>
+ <source>Ctrl+Q</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="164"/>
+ <source>Bye</source>
+ <translation>Ciao</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="168"/>
+ <source>&Website</source>
+ <translation>Sito &web</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="170"/>
+ <source>Minitube on the Web</source>
+ <translation>Minitube sul Web</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="174"/>
+ <source>&Donate via PayPal</source>
+ <translation>Fai una &donazione con PayPal</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="175"/>
+ <source>Please support the continued development of %1</source>
+ <translation>Supporta lo sviluppo di %1</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="179"/>
+ <source>&About</source>
+ <translation>&Informazioni</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="180"/>
+ <source>Info about %1</source>
+ <translation>Informazioni su %1</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="184"/>
+ <source>&Search</source>
+ <translation>&Ricerca</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="210"/>
+ <source>&Application</source>
+ <translation>&Applicazione</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="218"/>
+ <source>&Playlist</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="225"/>
+ <source>&Video</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="236"/>
+ <source>&Help</source>
+ <translation>&Aiuto</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="360"/>
+ <location filename="../src/MainWindow.cpp" line="366"/>
+ <source>Opening %1</source>
+ <translation>Apertura di %1</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="432"/>
+ <source>&Play</source>
+ <translation></translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="433"/>
+ <source>Resume playback</source>
+ <translation>Continua</translation>
+ </message>
+ <message>
+ <location filename="../src/MainWindow.cpp" line="462"/>
+ <source>Exit &Full Screen</source>
+ <translation>&Esci dallo schermo intero</translation>
+ </message>
+</context>
+<context>
+ <name>MediaView</name>
+ <message>
+ <location filename="../src/MediaView.cpp" line="24"/>
+ <source>Most relevant</source>
+ <translation>Più rilevanti</translation>
+ </message>
+ <message>
+ <location filename="../src/MediaView.cpp" line="27"/>
+ <source>Most recent</source>
+ <translation>Più recenti</translation>
+ </message>
+ <message>
+ <location filename="../src/MediaView.cpp" line="30"/>
+ <source>Most viewed</source>
+ <translation>Più visti</translation>
+ </message>
+ <message>
+ <location filename="../src/MediaView.h" line="29"/>
+ <source>You're watching "%1"</source>
+ <translation>Stai guardando "%1"</translation>
+ </message>
+</context>
+<context>
+ <name>PrettyItemDelegate</name>
+ <message>
+ <location filename="../src/playlist/PrettyItemDelegate.cpp" line="122"/>
+ <source>%1 views</source>
+ <translation>%1 visualizzazioni</translation>
+ </message>
+</context>
+<context>
+ <name>SearchLineEdit</name>
+ <message>
+ <location filename="../src/searchlineedit.cpp" line="171"/>
+ <source>Search</source>
+ <translation>Cerca</translation>
+ </message>
+</context>
+<context>
+ <name>SearchView</name>
+ <message>
+ <location filename="../src/SearchView.cpp" line="27"/>
+ <source>Welcome to <a href='%1'>%2</a>,</source>
+ <translation>Benvenuto su <a href="%1">%2</a>,</translation>
+ </message>
+ <message>
+ <location filename="../src/SearchView.cpp" line="34"/>
+ <source>Enter a keyword to start watching videos.</source>
+ <translation>Scrivi una parola chiave per iniziare a guardare i video.</translation>
+ </message>
+ <message>
+ <location filename="../src/SearchView.cpp" line="52"/>
+ <source>Watch</source>
+ <translation>Guarda</translation>
+ </message>
+ <message>
+ <location filename="../src/SearchView.cpp" line="67"/>
+ <source>Recent keywords</source>
+ <translation>Ultime ricerche</translation>
+ </message>
+ <message>
+ <location filename="../src/SearchView.cpp" line="181"/>
+ <source>A new version of %1 is available. Please <a href='%2'>update to version %3</a></source>
+ <translation>È disponibile una nuova versione di %1. <a href='%2'>Aggiorna alla versione %3</a></translation>
+ </message>
+ <message>
+ <location filename="../src/SearchView.h" line="27"/>
+ <source>Make yourself comfortable</source>
+ <translation>Mettiti comodo</translation>
+ </message>
+</context>
+<context>
+ <name>SettingsView</name>
+ <message>
+ <location filename="../src/SettingsView.h" line="17"/>
+ <source>Preferences</source>
+ <translation>Opzioni</translation>
+ </message>
+</context>
+</TS>
--- /dev/null
+# This voodoo comes from the Arora project
+
+INCLUDEPATH += $$PWD
+DEPENDPATH += $$PWD
+
+TRANSLATIONS += \
+ it_IT.ts \
+ pt_BR.ts \
+ ru_RU.ts
+
+isEmpty(QMAKE_LRELEASE) {
+ win32:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]\lrelease.exe -silent
+ else:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease -silent
+}
+
+updateqm.input = TRANSLATIONS
+updateqm.output = build/target/locale/${QMAKE_FILE_BASE}.qm
+updateqm.commands = $$QMAKE_LRELEASE ${QMAKE_FILE_IN} -qm build/target/locale/${QMAKE_FILE_BASE}.qm
+updateqm.CONFIG += no_link target_predeps
+QMAKE_EXTRA_COMPILERS += updateqm
+
+#qmfiles.files = TRANSLATIONS
+#qmfiles.path = Content/Resources
+#QMAKE_BUNDLE_DATA += qmfiles
--- /dev/null
+#!/bin/bash
+#
+# This script was written to update all the .ts files in one go
+#
+# This script is donated to the public domain
+#
+# Flavio Tordini, 2009
+
+for I in `ls -1 *.ts`;
+do
+ echo Updating $I
+ lupdate-qt4 ../minitube.pro -ts $I
+done
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS><TS version="1.1" language="pt_BR">
+<defaultcodec>UTF-8</defaultcodec>
+<context>
+ <name>AboutView</name>
+ <message>
+ <location filename="src/AboutView.cpp" line="20"/>
+ <source>There's life outside the browser!</source>
+ <translation>Não há vida fora do navegador!</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.cpp" line="21"/>
+ <source>Version %1</source>
+ <translation>Versão %1</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.cpp" line="24"/>
+ <source>This is a "Technology Preview" release, do not expect it to be perfect.</source>
+ <translation>Este é um lançamento "Prévio da Tecnologia", não esperamos que ela seja perfeita.</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.cpp" line="25"/>
+ <source>Report bugs and send in your ideas to %1</source>
+ <translation>Relate as falhas e envie suas ideias para %1</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.cpp" line="28"/>
+ <source>%1 is Free Software but its development takes precious time.</source>
+ <translation>%1 é um Software livre, mas o seu desenvolvimento tem um tempo precioso.</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.cpp" line="29"/>
+ <source>Please <a href='%1'>donate via PayPal</a> to support the continued development of %2.</source>
+ <translation>Por favor, <a href='%1'>doções via PayPal</a> para apoiar o desenvolvimento contínuo de %2.</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.cpp" line="32"/>
+ <source>Icon designed by %1.</source>
+ <translation>Ícone desenhado por %1.</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.cpp" line="33"/>
+ <source>Released under the <a href='%1'>GNU General Public License</a></source>
+ <translation>Lançado sob a <a href='%1'>Licença Pública Geral GNU</a></translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.h" line="18"/>
+ <source>About</source>
+ <translation>Sobre</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.h" line="20"/>
+ <source>What you always wanted to know about %1 and never dared to ask</source>
+ <translation>O que você sempre quis saber sobre %1 e nunca se atreveu a perguntar</translation>
+ </message>
+</context>
+<context>
+ <name>ClearButton</name>
+ <message>
+ <location filename="src/searchlineedit.cpp" line="50"/>
+ <source>Clear</source>
+ <translation>Limpar</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="src/MainWindow.cpp" line="89"/>
+ <source>&Back</source>
+ <translation>&Voltar</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="91"/>
+ <source>Alt+Left</source>
+ <translation>Alt+Left</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="92"/>
+ <source>Go to the previous view</source>
+ <translation>Ir para a visualização anterior</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="96"/>
+ <source>&Stop</source>
+ <translation>&Parar</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="97"/>
+ <source>Stop playback and go back to the search view</source>
+ <translation>Parar a reprodução e voltar à visualização da pesquisa</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="461"/>
+ <source>Esc</source>
+ <translation>Esc</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="102"/>
+ <source>S&kip</source>
+ <translation>Pu&lar</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="103"/>
+ <source>Skip to the next video</source>
+ <translation>Pular para o próximo vídeo</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="104"/>
+ <source>Ctrl+Right</source>
+ <translation>Ctrl+Right</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="418"/>
+ <source>&Pause</source>
+ <translation>&Pausar</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="419"/>
+ <source>Pause playback</source>
+ <translation>Pausar a reprodução</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="111"/>
+ <source>Space</source>
+ <translation>Barra de espaço</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="456"/>
+ <source>&Full Screen</source>
+ <translation>&Tela Cheia</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="117"/>
+ <source>Go full screen</source>
+ <translation>Ir para a tela cheia</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="455"/>
+ <source>Alt+Return</source>
+ <translation>Alt+Enter</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="132"/>
+ <source>&YouTube</source>
+ <translation>&YouTube</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="133"/>
+ <source>Open the YouTube video page</source>
+ <translation>Abrir a página de vídeo do YouTube</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="134"/>
+ <source>Ctrl+Y</source>
+ <translation>Ctrl+Y</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="139"/>
+ <source>&Remove</source>
+ <translation>&Remover</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="140"/>
+ <source>Remove the selected videos from the playlist</source>
+ <translation>Remover os vídeos selecionados da playlist</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="148"/>
+ <source>Move &Up</source>
+ <translation>Mover para &cima</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="149"/>
+ <source>Move up the selected videos in the playlist</source>
+ <translation>Mover para cima os vídeos selecionados na playlist</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="150"/>
+ <source>Ctrl+Up</source>
+ <translation>Ctrl+Up</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="155"/>
+ <source>Move &Down</source>
+ <translation>Mover para &baixo</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="156"/>
+ <source>Move down the selected videos in the playlist</source>
+ <translation>Mover para baixo os vídeos selecionados na playlist</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="157"/>
+ <source>Ctrl+Down</source>
+ <translation>Ctrl+Down</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="162"/>
+ <source>&Quit</source>
+ <translation>&Sair</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="163"/>
+ <source>Ctrl+Q</source>
+ <translation>Ctrl+Q</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="164"/>
+ <source>Bye</source>
+ <translation>Tchau</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="168"/>
+ <source>&Website</source>
+ <translation>&Website</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="170"/>
+ <source>Minitube on the Web</source>
+ <translation>Minitube na Web</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="174"/>
+ <source>&Donate via PayPal</source>
+ <translation>&Doações via PayPal</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="175"/>
+ <source>Please support the continued development of %1</source>
+ <translation>Por favor, apoiem o desenvolvimento contínuo de %1</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="179"/>
+ <source>&About</source>
+ <translation>&Sobre</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="180"/>
+ <source>Info about %1</source>
+ <translation>Informações sobre %1</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="184"/>
+ <source>&Search</source>
+ <translation>&Pesquisar</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="210"/>
+ <source>&Application</source>
+ <translation>&Aplicação</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="218"/>
+ <source>&Playlist</source>
+ <translation>&Playlist</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="225"/>
+ <source>&Video</source>
+ <translation>&Vídeo</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="236"/>
+ <source>&Help</source>
+ <translation>&Ajuda</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="366"/>
+ <source>Opening %1</source>
+ <translation>Abrindo %1</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="432"/>
+ <source>&Play</source>
+ <translation>&Reproduzir</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="433"/>
+ <source>Resume playback</source>
+ <translation>Continuar reprodução</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="462"/>
+ <source>Exit &Full Screen</source>
+ <translation>Sair de &Tela Cheia</translation>
+ </message>
+</context>
+<context>
+ <name>MediaView</name>
+ <message>
+ <location filename="src/MediaView.cpp" line="24"/>
+ <source>Most relevant</source>
+ <translation>Mais relevantes</translation>
+ </message>
+ <message>
+ <location filename="src/MediaView.cpp" line="27"/>
+ <source>Most recent</source>
+ <translation>Mais recentes</translation>
+ </message>
+ <message>
+ <location filename="src/MediaView.cpp" line="30"/>
+ <source>Most viewed</source>
+ <translation>Mais vistos</translation>
+ </message>
+ <message>
+ <location filename="src/MediaView.h" line="29"/>
+ <source>You're watching "%1"</source>
+ <translation>Você está assistindo "%1"</translation>
+ </message>
+</context>
+<context>
+ <name>PrettyItemDelegate</name>
+ <message>
+ <location filename="src/playlist/PrettyItemDelegate.cpp" line="122"/>
+ <source>%1 views</source>
+ <translation>%1 exibições</translation>
+ </message>
+</context>
+<context>
+ <name>SearchLineEdit</name>
+ <message>
+ <location filename="src/searchlineedit.cpp" line="171"/>
+ <source>Search</source>
+ <translation>Pesquisar</translation>
+ </message>
+</context>
+<context>
+ <name>SearchView</name>
+ <message>
+ <location filename="src/SearchView.cpp" line="27"/>
+ <source>Welcome to <a href='%1'>%2</a>,</source>
+ <translation>Bem-vindo ao <a href='%1'>%2</a>,</translation>
+ </message>
+ <message>
+ <location filename="src/SearchView.cpp" line="34"/>
+ <source>Enter a keyword to start watching videos.</source>
+ <translation>Digite uma palavra-chave para começar a assistir os vídeos.</translation>
+ </message>
+ <message>
+ <location filename="src/SearchView.cpp" line="52"/>
+ <source>Watch</source>
+ <translation>Assistir</translation>
+ </message>
+ <message>
+ <location filename="src/SearchView.cpp" line="67"/>
+ <source>Recent keywords</source>
+ <translation>Palavra-chave recente</translation>
+ </message>
+ <message>
+ <location filename="src/SearchView.cpp" line="181"/>
+ <source>A new version of %1 is available. Please <a href='%2'>update to version %3</a></source>
+ <translation>Um nova versão de %1 está disponível. Por favor, <a href='%2'>atualize para a versão %3</a></translation>
+ </message>
+ <message>
+ <location filename="src/SearchView.h" line="27"/>
+ <source>Make yourself comfortable</source>
+ <translation>Sinta-se confortável</translation>
+ </message>
+</context>
+<context>
+ <name>SettingsView</name>
+ <message>
+ <location filename="src/SettingsView.h" line="17"/>
+ <source>Preferences</source>
+ <translation>Preferências</translation>
+ </message>
+</context>
+</TS>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="ru_RU">
+<defaultcodec>UTF-8</defaultcodec>
+<context>
+ <name>AboutView</name>
+ <message>
+ <location filename="src/AboutView.cpp" line="20"/>
+ <source>There's life outside the browser!</source>
+ <translation>Это жизнь за пределами браузера!</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.cpp" line="21"/>
+ <source>Version %1</source>
+ <translation>Версия %1</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.cpp" line="24"/>
+ <source>This is a "Technology Preview" release, do not expect it to be perfect.</source>
+ <translation>Эта версия - "технический просмотр", не стоит ожидать совершенства.</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.cpp" line="25"/>
+ <source>Report bugs and send in your ideas to %1</source>
+ <translation>Сообщения об ошибках и идеи следует отправлять на %1</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.cpp" line="28"/>
+ <source>%1 is Free Software but its development takes precious time.</source>
+ <translation>%1 - свободное ПО, но его разработка отнимает драгоценное время.</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.cpp" line="29"/>
+ <source>Please <a href='%1'>donate via PayPal</a> to support the continued development of %2.</source>
+ <translation><a href='%1'>Поддержите через PayPal</a> дальнейшую разработку %2.</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.cpp" line="32"/>
+ <source>Icon designed by %1.</source>
+ <translation>Значок создан %1.</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.cpp" line="33"/>
+ <source>Released under the <a href='%1'>GNU General Public License</a></source>
+ <translation>Выпущено на условиях <a href='%1'>GNU General Public License</a></translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.h" line="18"/>
+ <source>About</source>
+ <translation>О программе</translation>
+ </message>
+ <message>
+ <location filename="src/AboutView.h" line="20"/>
+ <source>What you always wanted to know about %1 and never dared to ask</source>
+ <translation>Все что Вы всегда хотели узнать о %1 и никогда бы не спросили</translation>
+ </message>
+</context>
+<context>
+ <name>ClearButton</name>
+ <message>
+ <location filename="src/searchlineedit.cpp" line="50"/>
+ <source>Clear</source>
+ <translation>Очистить</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="src/MainWindow.cpp" line="89"/>
+ <source>&Back</source>
+ <translation>&Назад</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="91"/>
+ <source>Alt+Left</source>
+ <translation>Alt+стрелка назад</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="92"/>
+ <source>Go to the previous view</source>
+ <translation>Перейти к предыдущему</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="96"/>
+ <source>&Stop</source>
+ <translation>&Остановить</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="97"/>
+ <source>Stop playback and go back to the search view</source>
+ <translation>Остановить воспроизведение и вернуться к поиску</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="98"/>
+ <location filename="src/MainWindow.cpp" line="457"/>
+ <location filename="src/MainWindow.cpp" line="461"/>
+ <source>Esc</source>
+ <translation>Esc</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="102"/>
+ <source>S&kip</source>
+ <translation>П&ропустить</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="103"/>
+ <source>Skip to the next video</source>
+ <translation>Перейти к следующему видео</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="104"/>
+ <source>Ctrl+Right</source>
+ <translation>Ctrl+стрелка вправо</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="109"/>
+ <location filename="src/MainWindow.cpp" line="418"/>
+ <source>&Pause</source>
+ <translation>&Приостановить</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="110"/>
+ <location filename="src/MainWindow.cpp" line="419"/>
+ <source>Pause playback</source>
+ <translation>Приостановить воспроизведение</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="111"/>
+ <source>Space</source>
+ <translation>Пробел</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="116"/>
+ <location filename="src/MainWindow.cpp" line="456"/>
+ <source>&Full Screen</source>
+ <translation>&На весь экран</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="117"/>
+ <source>Go full screen</source>
+ <translation>Полноэкранное воспроизведение</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="118"/>
+ <location filename="src/MainWindow.cpp" line="455"/>
+ <source>Alt+Return</source>
+ <translation>Alt+Enter</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="132"/>
+ <source>&YouTube</source>
+ <translation>&YouTube</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="133"/>
+ <source>Open the YouTube video page</source>
+ <translation>Открыть страницу видео в YouTube</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="134"/>
+ <source>Ctrl+Y</source>
+ <translation>Ctrl+Y</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="139"/>
+ <source>&Remove</source>
+ <translation>&Удалить</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="140"/>
+ <source>Remove the selected videos from the playlist</source>
+ <translation>Удалить выбранные видеоклипы из списка воспроизведения</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="148"/>
+ <source>Move &Up</source>
+ <translation>В&верх</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="149"/>
+ <source>Move up the selected videos in the playlist</source>
+ <translation>Сдвинуть выбранные видеоклипы вверх в списке воспроизведения</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="150"/>
+ <source>Ctrl+Up</source>
+ <translation>Ctrl+стрелка вверх</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="155"/>
+ <source>Move &Down</source>
+ <translation>В&низ</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="156"/>
+ <source>Move down the selected videos in the playlist</source>
+ <translation>Сдвинуть выбранные видеоклипы вниз в списке воспроизведения</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="157"/>
+ <source>Ctrl+Down</source>
+ <translation>Ctrl+стрелка вниз</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="162"/>
+ <source>&Quit</source>
+ <translation>&Выход</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="163"/>
+ <source>Ctrl+Q</source>
+ <translation>Ctrl+Q</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="164"/>
+ <source>Bye</source>
+ <translation>Пока</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="168"/>
+ <source>&Website</source>
+ <translation>&Домашняя страница</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="170"/>
+ <source>Minitube on the Web</source>
+ <translation>Minitube в интернете</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="174"/>
+ <source>&Donate via PayPal</source>
+ <translation>&Поддержать через PayPal</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="175"/>
+ <source>Please support the continued development of %1</source>
+ <translation>Поддержите дальнейшую разработку %1</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="179"/>
+ <source>&About</source>
+ <translation>&О программе</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="180"/>
+ <source>Info about %1</source>
+ <translation>Сведения о %1</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="184"/>
+ <source>&Search</source>
+ <translation>&Поиск</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="210"/>
+ <source>&Application</source>
+ <translation>Пр&иложение</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="218"/>
+ <source>&Playlist</source>
+ <translation>&Список воспроизведения</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="225"/>
+ <source>&Video</source>
+ <translation>&Видео</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="236"/>
+ <source>&Help</source>
+ <translation>Спр&авка</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="360"/>
+ <location filename="src/MainWindow.cpp" line="366"/>
+ <source>Opening %1</source>
+ <translation>Открытие %1</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="432"/>
+ <source>&Play</source>
+ <translation>Пр&оиграть</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="433"/>
+ <source>Resume playback</source>
+ <translation>Продолжить воспроизведение</translation>
+ </message>
+ <message>
+ <location filename="src/MainWindow.cpp" line="462"/>
+ <source>Exit &Full Screen</source>
+ <translation>В&ыйти из полноэкранного воспроизведения</translation>
+ </message>
+</context>
+<context>
+ <name>MediaView</name>
+ <message>
+ <location filename="src/MediaView.cpp" line="24"/>
+ <source>Most relevant</source>
+ <translation>Похожие видео</translation>
+ </message>
+ <message>
+ <location filename="src/MediaView.cpp" line="27"/>
+ <source>Most recent</source>
+ <translation>Недавно просмотренные</translation>
+ </message>
+ <message>
+ <location filename="src/MediaView.cpp" line="30"/>
+ <source>Most viewed</source>
+ <translation>Самые популярные</translation>
+ </message>
+ <message>
+ <location filename="src/MediaView.h" line="29"/>
+ <source>You're watching "%1"</source>
+ <translation>Сейчас просматривается "%1"</translation>
+ </message>
+</context>
+<context>
+ <name>PrettyItemDelegate</name>
+ <message>
+ <location filename="src/playlist/PrettyItemDelegate.cpp" line="122"/>
+ <source>%1 views</source>
+ <translation>%1 просмотров</translation>
+ </message>
+</context>
+<context>
+ <name>SearchLineEdit</name>
+ <message>
+ <location filename="src/searchlineedit.cpp" line="171"/>
+ <source>Search</source>
+ <translation>Поиск</translation>
+ </message>
+</context>
+<context>
+ <name>SearchView</name>
+ <message>
+ <location filename="src/SearchView.cpp" line="27"/>
+ <source>Welcome to <a href='%1'>%2</a>,</source>
+ <translation>Добро пожаловать в <a href='%1'>%2</a>,</translation>
+ </message>
+ <message>
+ <location filename="src/SearchView.cpp" line="34"/>
+ <source>Enter a keyword to start watching videos.</source>
+ <translation>Введите ключевые слова для начала просмотра видео.</translation>
+ </message>
+ <message>
+ <location filename="src/SearchView.cpp" line="52"/>
+ <source>Watch</source>
+ <translation>Смотреть</translation>
+ </message>
+ <message>
+ <location filename="src/SearchView.cpp" line="67"/>
+ <source>Recent keywords</source>
+ <translation>Последние</translation>
+ </message>
+ <message>
+ <location filename="src/SearchView.cpp" line="181"/>
+ <source>A new version of %1 is available. Please <a href='%2'>update to version %3</a></source>
+ <translation>Доступна новая версия %1. <a href='%2'>Обновите до %3</a></translation>
+ </message>
+ <message>
+ <location filename="src/SearchView.h" line="27"/>
+ <source>Make yourself comfortable</source>
+ <translation>Чувствуйте себя как дома</translation>
+ </message>
+</context>
+<context>
+ <name>SettingsView</name>
+ <message>
+ <location filename="src/SettingsView.h" line="17"/>
+ <source>Preferences</source>
+ <translation>Настройки</translation>
+ </message>
+</context>
+</TS>
--- /dev/null
+# If Phonon cannot be found, uncomment the following (and set the correct path)
+# INCLUDEPATH += /usr/include/phonon
+CONFIG += release
+
+TEMPLATE = app
+
+# Saner string behaviour
+#DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII QT_STRICT_ITERATORS
+
+TARGET = minitube
+mac {
+ TARGET = Minitube
+ QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.4
+}
+
+QT += network \
+ xml \
+ phonon
+include(src/qtsingleapplication/qtsingleapplication.pri)
+include(src/thlibrary/thlibrary.pri)
+HEADERS += src/MainWindow.h \
+ src/SearchView.h \
+ src/MediaView.h \
+ src/SettingsView.h \
+ src/AboutView.h \
+ src/youtubesearch.h \
+ src/video.h \
+ src/youtubestreamreader.h \
+ src/View.h \
+ src/searchlineedit.h \
+ src/urllineedit.h \
+ src/spacer.h \
+ src/Constants.h \
+ src/iconloader/qticonloader.h \
+ src/faderwidget/FaderWidget.h \
+ src/ListModel.h \
+ src/playlist/PrettyItemDelegate.h \
+ src/networkaccess.h \
+ src/videomimedata.h \
+ src/global.h \
+ src/updatechecker.h \
+ src/videowidget.h \
+ src/playlistwidget.h \
+ src/searchparams.h \
+ src/minisplitter.h
+SOURCES += src/main.cpp \
+ src/MainWindow.cpp \
+ src/SearchView.cpp \
+ src/MediaView.cpp \
+ src/SettingsView.cpp \
+ src/AboutView.cpp \
+ src/youtubesearch.cpp \
+ src/youtubestreamreader.cpp \
+ src/searchlineedit.cpp \
+ src/urllineedit.cpp \
+ src/spacer.cpp \
+ src/video.cpp \
+ src/iconloader/qticonloader.cpp \
+ src/faderwidget/FaderWidget.cpp \
+ src/ListModel.cpp \
+ src/playlist/PrettyItemDelegate.cpp \
+ src/videomimedata.cpp \
+ src/updatechecker.cpp \
+ src/videowidget.cpp \
+ src/networkaccess.cpp \
+ src/playlistwidget.cpp \
+ src/searchparams.cpp \
+ src/minisplitter.cpp
+RESOURCES += resources.qrc
+DESTDIR = build/target/
+OBJECTS_DIR = build/obj/
+MOC_DIR = build/moc/
+RCC_DIR = build/rcc/
+
+# Tell Qt Linguist that we use UTF-8 strings in our sources
+CODECFORTR = UTF-8
+CODECFORSRC = UTF-8
+include(locale/locale.pri)
+
+# deploy
+
+DISTFILES += CHANGES \
+ LICENSE
+
+mac {
+ CONFIG += x86 \
+ ppc
+ QMAKE_INFO_PLIST = Info.plist
+ ICON = minitube.icns
+}
+
+unix {
+ isEmpty(PREFIX) {
+ PREFIX = /usr/local
+ }
+ BINDIR = $$PREFIX/bin
+
+ INSTALLS += target
+ target.path = $$BINDIR
+
+ DATADIR = $$PREFIX/share
+ PKGDATADIR = $$DATADIR/minitube
+ DEFINES += DATADIR=\\\"$$DATADIR\\\" PKGDATADIR=\\\"$$PKGDATADIR\\\"
+}
--- /dev/null
+<RCC>
+ <qresource prefix="/" >
+ <file>images/app.png</file>
+ <file>images/pause.png</file>
+ <file>images/play.png</file>
+ <file>images/skip.png</file>
+ <file>images/stop.png</file>
+ <file>images/go-previous.png</file>
+ <file>images/view-fullscreen.png</file>
+ <file>images/go-down.png</file>
+ <file>images/go-up.png</file>
+ <file>images/internet-web-browser.png</file>
+ </qresource>
+</RCC>
--- /dev/null
+#include "AboutView.h"
+#include "Constants.h"
+
+AboutView::AboutView(QWidget *parent) : QWidget(parent) {
+
+ QBoxLayout *aboutlayout = new QHBoxLayout(this);
+ aboutlayout->setAlignment(Qt::AlignCenter);
+ aboutlayout->setSpacing(30);
+
+ QLabel *logo = new QLabel(this);
+ logo->setPixmap(QPixmap(":/images/app.png"));
+ aboutlayout->addWidget(logo, 0, Qt::AlignTop);
+
+ QBoxLayout *layout = new QVBoxLayout();
+ layout->setAlignment(Qt::AlignCenter);
+ layout->setSpacing(30);
+ aboutlayout->addLayout(layout);
+
+ QString info = "<h1>" + QString(Constants::APP_NAME) + "</h1>"
+ "<p>" + tr("There's life outside the browser!") + "</p>"
+ "<p>" + tr("Version %1").arg(Constants::VERSION) + "</p>"
+ + QString("<p><a href=\"%1/\">%1</a></p>").arg(Constants::WEBSITE) +
+
+ "<p>" + tr("This is a \"Technology Preview\" release, do not expect it to be perfect.") + "<br/>"
+ + tr("Report bugs and send in your ideas to %1")
+ .arg(QString("<a href=\"mailto:%1\">%1</a>").arg(Constants::EMAIL)) + "</p>"
+
+ "<p>" + tr("%1 is Free Software but its development takes precious time.").arg(Constants::APP_NAME) + "<br/>"
+ + tr("Please <a href='%1'>donate via PayPal</a> to support the continued development of %2.")
+ .arg(QString(Constants::WEBSITE).append("#donate"), Constants::APP_NAME) + "</p>"
+
+ "<p>" + tr("Icon designed by %1.").arg("Sebastian Kraft") + "</p>"
+
+ "<p>" + tr("Translated by %1").arg("Nikita Lyalin (ru_RU), Márcio Moraes (pt_BR)") + "</p>"
+
+ "<p>" + tr("Released under the <a href='%1'>GNU General Public License</a>")
+ .arg("http://www.gnu.org/licenses/gpl.html") + "</p>"
+
+ "<p>© 2009 " + Constants::ORG_NAME + "</p>";
+ QLabel *infoLabel = new QLabel(info, this);
+ infoLabel->setOpenExternalLinks(true);
+ layout->addWidget(infoLabel);
+
+ QLayout *buttonLayout = new QHBoxLayout();
+ buttonLayout->setAlignment(Qt::AlignLeft);
+ QPushButton *closeButton = new QPushButton("&Close", this);
+ closeButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+
+ closeButton->setDefault(true);
+ closeButton->setFocus(Qt::OtherFocusReason);
+ connect(closeButton, SIGNAL(clicked()), parent, SLOT(goBack()));
+ buttonLayout->addWidget(closeButton);
+
+ layout->addLayout(buttonLayout);
+
+}
+
+void AboutView::paintEvent(QPaintEvent * /*event*/) {
+
+ QPainter painter(this);
+
+#ifdef Q_WS_MAC
+ QLinearGradient linearGrad(0, 0, 0, height());
+ QPalette palette;
+ linearGrad.setColorAt(0, palette.color(QPalette::Light));
+ linearGrad.setColorAt(1, palette.color(QPalette::Midlight));
+ painter.fillRect(0, 0, width(), height(), QBrush(linearGrad));
+#endif
+
+}
--- /dev/null
+#ifndef ABOUTVIEW_H
+#define ABOUTVIEW_H
+
+#include <QtGui>
+#include "View.h"
+#include "Constants.h"
+
+class AboutView : public QWidget, public View {
+
+ Q_OBJECT
+
+public:
+ AboutView(QWidget *parent);
+ void appear() {}
+ void disappear() {}
+ QMap<QString, QVariant> metadata() {
+ QMap<QString, QVariant> metadata;
+ metadata.insert("title", tr("About"));
+ metadata.insert("description",
+ tr("What you always wanted to know about %1 and never dared to ask")
+ .arg(Constants::APP_NAME));
+ return metadata;
+ }
+
+protected:
+ void paintEvent(QPaintEvent *);
+
+};
+#endif
--- /dev/null
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+namespace Constants {
+ static const char *VERSION = "0.3";
+ static const char *APP_NAME = "Minitube";
+ static const char *ORG_NAME = "Flavio Tordini";
+ static const char *ORG_DOMAIN = "flavio.tordini.org";
+ static const char *WEBSITE = "http://flavio.tordini.org/minitube";
+ static const char *EMAIL = "flavio.tordini@gmail.com";
+ static const QString USER_AGENT = QString(APP_NAME) + " " + VERSION + " (" + WEBSITE + ")";
+}
+
+#endif
--- /dev/null
+#include "ListModel.h"
+#include "videomimedata.h"
+
+#define MAX_ITEMS 10
+
+ListModel::ListModel(QWidget *parent) : QAbstractListModel(parent) {
+ youtubeSearch = 0;
+ searching = false;
+ canSearchMore = true;
+ m_activeVideo = 0;
+ m_activeRow = -1;
+ skip = 1;
+}
+
+ListModel::~ListModel() {
+ delete youtubeSearch;
+}
+
+int ListModel::rowCount(const QModelIndex &/*parent*/) const {
+ int count = videos.size();
+
+ // add the message item
+ if (videos.isEmpty() || !searching)
+ count++;
+
+ return count;
+}
+
+QVariant ListModel::data(const QModelIndex &index, int role) const {
+
+ int row = index.row();
+
+ if (row == videos.size()) {
+
+ QPalette palette;
+ QFont boldFont;
+ boldFont.setBold(true);
+
+ switch (role) {
+ case ItemTypeRole:
+ return ItemTypeShowMore;
+ case Qt::DisplayRole:
+ case Qt::StatusTipRole:
+ if (searching) return tr("Searching...");
+ if (canSearchMore) return tr("Show %1 More").arg(MAX_ITEMS);
+ if (videos.isEmpty()) return tr("No videos");
+ else return tr("No more videos");
+ case Qt::TextAlignmentRole:
+ return QVariant(int(Qt::AlignHCenter | Qt::AlignVCenter));
+ case Qt::ForegroundRole:
+ return palette.color(QPalette::Dark);
+ case Qt::FontRole:
+ return boldFont;
+ default:
+ return QVariant();
+ }
+
+ } else if (row < 0 || row >= videos.size())
+ return QVariant();
+
+ Video *video = videos.at(row);
+
+ switch (role) {
+ case ItemTypeRole:
+ return ItemTypeVideo;
+ case VideoRole:
+ return QVariant::fromValue(QPointer<Video>(video));
+ case ActiveTrackRole:
+ return video == m_activeVideo;
+ case Qt::DisplayRole:
+ case Qt::StatusTipRole:
+ return video->title();
+ /*
+ case Qt::ToolTipRole:
+
+ QString tooltip;
+ if (!element.firstChildElement().text().isEmpty()) {
+ tooltip.append(QString("<b>").append(element.firstChildElement().text()).append("</b><br/>"));
+ }
+ if (!fromDate.isEmpty()) {
+ tooltip.append("<i>Pubblicato il</i> ").append(fromDate);
+ }
+ if (!toDate.isEmpty()) {
+ tooltip.append("<br/><i>Scadenza</i>: ").append(toDate);
+ }
+ tooltip.append("<br/><i>Tipo</i>: ").append(typeName)
+ .append("<br/><i>Id</i>: ").appen QFont boldFont;
+ boldFont.setBold(true);d(id);
+ return tooltip;
+ */
+
+ case StreamUrlRole:
+ return video->streamUrl();
+ }
+
+ return QVariant();
+}
+
+void ListModel::setActiveRow( int row) {
+ if ( rowExists( row ) ) {
+
+ m_activeRow = row;
+ m_activeVideo = videoAt(row);
+
+ // setStateOfRow( row, Item::Played );
+
+ int oldactiverow = m_activeRow;
+
+ if ( rowExists( oldactiverow ) )
+ emit dataChanged( createIndex( oldactiverow, 0 ), createIndex( oldactiverow, columnCount() - 1 ) );
+
+ emit dataChanged( createIndex( m_activeRow, 0 ), createIndex( m_activeRow, columnCount() - 1 ) );
+ emit activeRowChanged(row);
+
+ } else {
+ m_activeRow = -1;
+ m_activeVideo = 0;
+ }
+
+}
+
+int ListModel::nextRow() const {
+ int nextRow = m_activeRow + 1;
+ if (rowExists(nextRow))
+ return nextRow;
+ return -1;
+}
+
+Video* ListModel::videoAt( int row ) const {
+ if ( rowExists( row ) )
+ return videos.at( row );
+ return 0;
+}
+
+Video* ListModel::activeVideo() const {
+ return m_activeVideo;
+}
+
+void ListModel::search(SearchParams *searchParams) {
+
+ // delete current videos
+ while (!videos.isEmpty())
+ delete videos.takeFirst();
+ m_activeVideo = 0;
+ m_activeRow = -1;
+ skip = 1;
+ reset();
+
+ // (re)initialize the YouTubeSearch
+ if (youtubeSearch) delete youtubeSearch;
+ youtubeSearch = new YouTubeSearch();
+ connect(youtubeSearch, SIGNAL(gotVideo(Video*)), this, SLOT(addVideo(Video*)));
+ connect(youtubeSearch, SIGNAL(finished(int)), this, SLOT(searchFinished(int)));
+
+ this->searchParams = searchParams;
+ searching = true;
+ youtubeSearch->search(searchParams, MAX_ITEMS, skip);
+ skip += MAX_ITEMS;
+}
+
+void ListModel::searchMore(int max) {
+ if (searching) return;
+ searching = true;
+ youtubeSearch->search(searchParams, max, skip);
+ skip += max;
+}
+
+void ListModel::searchMore() {
+ searchMore(MAX_ITEMS);
+}
+
+void ListModel::searchNeeded() {
+ int remainingRows = videos.size() - m_activeRow;
+ int rowsNeeded = MAX_ITEMS - remainingRows;
+ if (rowsNeeded > 0)
+ searchMore(rowsNeeded);
+}
+
+void ListModel::abortSearch() {
+ while (!videos.isEmpty())
+ delete videos.takeFirst();
+ reset();
+ youtubeSearch->abort();
+ searching = false;
+}
+
+void ListModel::searchFinished(int total) {
+ searching = false;
+ canSearchMore = total > 0;
+}
+
+void ListModel::addVideo(Video* video) {
+
+ connect(video, SIGNAL(gotThumbnail()), this, SLOT(updateThumbnail()));
+
+ beginInsertRows(QModelIndex(), videos.size(), videos.size());
+ videos << video;
+ endInsertRows();
+
+ // autoplay
+ if (videos.size() == 1) {
+ setActiveRow(0);
+ }
+
+}
+
+void ListModel::updateThumbnail() {
+
+ Video *video = static_cast<Video *>(sender());
+ if (!video) {
+ qDebug() << "Cannot get sender";
+ return;
+ }
+
+ int row = rowForVideo(video);
+ emit dataChanged( createIndex( row, 0 ), createIndex( row, columnCount() - 1 ) );
+
+}
+
+// --- item removal
+
+/**
+ * This function does not free memory
+ */
+bool ListModel::removeRows(int position, int rows, const QModelIndex & /*parent*/) {
+ beginRemoveRows(QModelIndex(), position, position+rows-1);
+ for (int row = 0; row < rows; ++row) {
+ videos.removeAt(position);
+ }
+ endRemoveRows();
+ return true;
+}
+
+void ListModel::removeIndexes(QModelIndexList &indexes) {
+ QList<Video*> originalList(videos);
+ QList<Video*> delitems;
+ foreach (QModelIndex index, indexes) {
+ Video* video = originalList.at(index.row());
+ int idx = videos.indexOf(video);
+ if (idx != -1) {
+ beginRemoveRows(QModelIndex(), idx, idx);
+ delitems.append(video);
+ videos.removeAll(video);
+ endRemoveRows();
+ }
+ }
+
+ qDeleteAll(delitems);
+
+}
+
+// --- Sturm und drang ---
+
+
+
+Qt::DropActions ListModel::supportedDropActions() const {
+ return Qt::MoveAction;
+}
+
+Qt::ItemFlags ListModel::flags(const QModelIndex &index) const {
+ Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
+
+ if (index.isValid()) {
+ if (index.row() == videos.size()) {
+ // don't drag the "show 10 more" item
+ return defaultFlags;
+ } else
+ return ( defaultFlags | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled );
+ } else
+ return Qt::ItemIsDropEnabled | defaultFlags;
+}
+
+QStringList ListModel::mimeTypes() const {
+ QStringList types;
+ types << "application/x-minitube-video";
+ return types;
+}
+
+QMimeData* ListModel::mimeData( const QModelIndexList &indexes ) const {
+ VideoMimeData* mime = new VideoMimeData();
+
+ foreach( const QModelIndex &it, indexes ) {
+ int row = it.row();
+ if (row >= 0 && row < videos.size())
+ mime->addVideo( videos.at( it.row() ) );
+ }
+
+ return mime;
+}
+
+bool ListModel::dropMimeData(const QMimeData *data,
+ Qt::DropAction action, int row, int column,
+ const QModelIndex &parent) {
+ if (action == Qt::IgnoreAction)
+ return true;
+
+ if (!data->hasFormat("application/x-minitube-video"))
+ return false;
+
+ if (column > 0)
+ return false;
+
+ int beginRow;
+ if (row != -1)
+ beginRow = row;
+ else if (parent.isValid())
+ beginRow = parent.row();
+ else
+ beginRow = rowCount(QModelIndex());
+
+ const VideoMimeData* videoMimeData = dynamic_cast<const VideoMimeData*>( data );
+ if(!videoMimeData ) return false;
+
+ QList<Video*> droppedVideos = videoMimeData->videos();
+ foreach( Video *video, droppedVideos) {
+
+ // remove videos
+ int videoRow = videos.indexOf(video);
+ removeRows(videoRow, 1, QModelIndex());
+
+ // and then add them again at the new position
+ beginInsertRows(QModelIndex(), beginRow, beginRow);
+ videos.insert(beginRow, video);
+ endInsertRows();
+
+ }
+
+ // fix m_activeRow after all this
+ m_activeRow = videos.indexOf(m_activeVideo);
+
+ // let the MediaView restore the selection
+ emit needSelectionFor(droppedVideos);
+
+ return true;
+
+}
+
+int ListModel::rowForVideo(Video* video) {
+ return videos.indexOf(video);
+}
+
+QModelIndex ListModel::indexForVideo(Video* video) {
+ return createIndex(videos.indexOf(video), 0);
+}
+
+void ListModel::move(QModelIndexList &indexes, bool up) {
+
+ QList<Video*> movedVideos;
+
+ foreach (QModelIndex index, indexes) {
+ int row = index.row();
+ qDebug() << "index row" << row;
+ Video *video = videoAt(row);
+ movedVideos << video;
+ }
+
+ int counter = 1;
+ foreach (Video *video, movedVideos) {
+
+ int row = rowForVideo(video);
+ qDebug() << "video row" << row;
+ removeRows(row, 1, QModelIndex());
+
+ if (up) row--;
+ else row++;
+
+ beginInsertRows(QModelIndex(), row, row);
+ videos.insert(row, video);
+ endInsertRows();
+
+ counter++;
+ }
+
+ emit needSelectionFor(movedVideos);
+
+}
--- /dev/null
+#ifndef LISTMODEL_H
+#define LISTMODEL_H
+
+#include "video.h"
+#include "youtubesearch.h"
+#include "searchparams.h"
+
+enum DataRoles {
+ ItemTypeRole = Qt::UserRole,
+ VideoRole,
+ StreamUrlRole,
+ ActiveTrackRole
+};
+
+enum ItemTypes {
+ ItemTypeVideo = 1,
+ ItemTypeShowMore
+};
+
+class ListModel : public QAbstractListModel {
+
+ Q_OBJECT
+
+public:
+
+ ListModel(QWidget *parent);
+ ~ListModel();
+
+ // inherited from QAbstractListModel
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ // int rowCount( const QModelIndex& parent = QModelIndex() ) const { Q_UNUSED( parent ); return videos.size(); }
+ int columnCount( const QModelIndex& parent = QModelIndex() ) const { Q_UNUSED( parent ); return 4; }
+ QVariant data(const QModelIndex &index, int role) const;
+ bool removeRows(int position, int rows, const QModelIndex &parent);
+
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+ QStringList mimeTypes() const;
+ Qt::DropActions supportedDropActions() const;
+ QMimeData* mimeData( const QModelIndexList &indexes ) const;
+ bool dropMimeData(const QMimeData *data,
+ Qt::DropAction action, int row, int column,
+ const QModelIndex &parent);
+
+ // custom methods
+ void setActiveRow( int row );
+ bool rowExists( int row ) const { return (( row >= 0 ) && ( row < videos.size() ) ); }
+ // int activeRow() const { return m_activeRow; } // returns -1 if there is no active row
+ int nextRow() const;
+ void removeIndexes(QModelIndexList &indexes);
+ int rowForVideo(Video* video);
+ QModelIndex indexForVideo(Video* video);
+ void move(QModelIndexList &indexes, bool up);
+
+ Video* videoAt( int row ) const;
+ Video* activeVideo() const;
+
+ // video search methods
+ void search(SearchParams *searchParams);
+ void abortSearch();
+
+
+public slots:
+ void searchMore();
+ void searchNeeded();
+ void addVideo(Video* video);
+ void searchFinished(int total);
+ void updateThumbnail();
+
+signals:
+ void activeRowChanged(int);
+ void needSelectionFor(QList<Video*>);
+
+private:
+ void searchMore(int max);
+
+ YouTubeSearch *youtubeSearch;
+ SearchParams *searchParams;
+ bool searching;
+ bool canSearchMore;
+
+ QList<Video*> videos;
+ int skip;
+
+ // the row being played
+ int m_activeRow;
+ Video *m_activeVideo;
+
+};
+
+#endif
--- /dev/null
+#include "MainWindow.h"
+#include "spacer.h"
+#include "Constants.h"
+#include "iconloader/qticonloader.h"
+#include "global.h"
+
+MainWindow::MainWindow() {
+
+ m_fullscreen = false;
+ mediaObject = 0;
+ audioOutput = 0;
+
+ // views mechanism
+ history = new QStack<QWidget*>();
+ views = new QStackedWidget(this);
+
+ // views
+ searchView = new SearchView(this);
+ connect(searchView, SIGNAL(search(QString)), this, SLOT(showMedia(QString)));
+ views->addWidget(searchView);
+ mediaView = new MediaView(this);
+ views->addWidget(mediaView);
+
+ // lazy initialized views
+ aboutView = 0;
+ settingsView = 0;
+
+ toolbarSearch = new SearchLineEdit(this);
+ toolbarSearch->setFont(qApp->font());
+ // toolbarSearch->setMinimumWidth(200);
+ connect(toolbarSearch, SIGNAL(search(const QString&)), searchView, SLOT(watch(const QString&)));
+
+ // build ui
+ createActions();
+ createMenus();
+ createToolBars();
+ createStatusBar();
+
+ // remove that useless menu/toolbar context menu
+ this->setContextMenuPolicy(Qt::NoContextMenu);
+
+ // mediaView init stuff thats needs actions
+ mediaView->initialize();
+
+ // restore window position
+ readSettings();
+
+ // show the initial view
+ showWidget(searchView);
+
+ setCentralWidget(views);
+
+ // top dock widget
+ /*
+ QLabel* message = new QLabel(this);
+ message->setText(QString("A new version of %1 is available.").arg(Constants::APP_NAME));
+ message->setMargin(10);
+ message->setAlignment(Qt::AlignCenter);
+ QPalette palette;
+ message->setBackgroundRole(QPalette::ToolTipBase);
+ message->setForegroundRole(QPalette::ToolTipText);
+ message->setAutoFillBackground(true);
+ QDockWidget *dockWidget = new QDockWidget("", this, 0);
+ dockWidget->setTitleBarWidget(0);
+ dockWidget->setWidget(message);
+ dockWidget->setFeatures(QDockWidget::DockWidgetClosable);
+ addDockWidget(Qt::TopDockWidgetArea, dockWidget);
+ */
+
+}
+
+MainWindow::~MainWindow() {
+ delete history;
+}
+
+void MainWindow::createActions() {
+
+ QMap<QString, QAction*> *actions = The::globalActions();
+
+ /*
+ settingsAct = new QAction(tr("&Preferences..."), this);
+ settingsAct->setStatusTip(tr(QString("Configure ").append(Constants::APP_NAME).toUtf8()));
+ // Mac integration
+ settingsAct->setMenuRole(QAction::PreferencesRole);
+ actions->insert("settings", settingsAct);
+ connect(settingsAct, SIGNAL(triggered()), this, SLOT(showSettings()));
+ */
+
+ backAct = new QAction(QIcon(":/images/go-previous.png"), tr("&Back"), this);
+ backAct->setEnabled(false);
+ backAct->setShortcut(tr("Alt+Left"));
+ backAct->setStatusTip(tr("Go to the previous view"));
+ actions->insert("back", backAct);
+ connect(backAct, SIGNAL(triggered()), this, SLOT(goBack()));
+
+ stopAct = new QAction(QtIconLoader::icon("media-stop", QIcon(":/images/stop.png")), tr("&Stop"), this);
+ stopAct->setStatusTip(tr("Stop playback and go back to the search view"));
+ stopAct->setShortcut(tr("Esc"));
+ actions->insert("stop", stopAct);
+ connect(stopAct, SIGNAL(triggered()), this, SLOT(stop()));
+
+ skipAct = new QAction(QtIconLoader::icon("media-skip-forward", QIcon(":/images/skip.png")), tr("S&kip"), this);
+ skipAct->setStatusTip(tr("Skip to the next video"));
+ skipAct->setShortcut(tr("Ctrl+Right"));
+ skipAct->setEnabled(false);
+ actions->insert("skip", skipAct);
+ connect(skipAct, SIGNAL(triggered()), mediaView, SLOT(skip()));
+
+ pauseAct = new QAction(QtIconLoader::icon("media-pause", QIcon(":/images/pause.png")), tr("&Pause"), this);
+ pauseAct->setStatusTip(tr("Pause playback"));
+ pauseAct->setShortcut(tr("Space"));
+ pauseAct->setEnabled(false);
+ actions->insert("pause", pauseAct);
+ connect(pauseAct, SIGNAL(triggered()), mediaView, SLOT(pause()));
+
+ fullscreenAct = new QAction(QtIconLoader::icon("view-fullscreen", QIcon(":/images/view-fullscreen.png")), tr("&Full Screen"), this);
+ fullscreenAct->setStatusTip(tr("Go full screen"));
+ fullscreenAct->setShortcut(tr("Alt+Return"));
+ fullscreenAct->setShortcutContext(Qt::ApplicationShortcut);
+ actions->insert("fullscreen", fullscreenAct);
+ connect(fullscreenAct, SIGNAL(triggered()), this, SLOT(fullscreen()));
+
+ /*
+ // icon should be document-save but it is ugly
+ downloadAct = new QAction(QtIconLoader::icon("go-down", QIcon(":/images/go-down.png")), tr("&Download"), this);
+ downloadAct->setStatusTip(tr("Download this video"));
+ downloadAct->setShortcut(tr("Ctrl+S"));
+ actions.insert("download", downloadAct);
+ connect(downloadAct, SIGNAL(triggered()), this, SLOT(download()));
+ */
+
+ webPageAct = new QAction(QtIconLoader::icon("internet-web-browser", QIcon(":/images/internet-web-browser.png")), tr("&YouTube"), this);
+ webPageAct->setStatusTip(tr("Open the YouTube video page"));
+ webPageAct->setShortcut(tr("Ctrl+Y"));
+ webPageAct->setEnabled(false);
+ actions->insert("webpage", webPageAct);
+ connect(webPageAct, SIGNAL(triggered()), mediaView, SLOT(openWebPage()));
+
+ removeAct = new QAction(tr("&Remove"), this);
+ removeAct->setStatusTip(tr("Remove the selected videos from the playlist"));
+ QList<QKeySequence> shortcuts;
+ shortcuts << QKeySequence("Del") << QKeySequence("Backspace");
+ removeAct->setShortcuts(shortcuts);
+ removeAct->setEnabled(false);
+ actions->insert("remove", removeAct);
+ connect(removeAct, SIGNAL(triggered()), mediaView, SLOT(removeSelected()));
+
+ moveUpAct = new QAction(QtIconLoader::icon("go-up", QIcon(":/images/go-up.png")), tr("Move &Up"), this);
+ moveUpAct->setStatusTip(tr("Move up the selected videos in the playlist"));
+ moveUpAct->setShortcut(tr("Ctrl+Up"));
+ moveUpAct->setEnabled(false);
+ actions->insert("moveUp", moveUpAct);
+ connect(moveUpAct, SIGNAL(triggered()), mediaView, SLOT(moveUpSelected()));
+
+ moveDownAct = new QAction(QtIconLoader::icon("go-down", QIcon(":/images/go-down.png")), tr("Move &Down"), this);
+ moveDownAct->setStatusTip(tr("Move down the selected videos in the playlist"));
+ moveDownAct->setShortcut(tr("Ctrl+Down"));
+ moveDownAct->setEnabled(false);
+ actions->insert("moveDown", moveDownAct);
+ connect(moveDownAct, SIGNAL(triggered()), mediaView, SLOT(moveDownSelected()));
+
+ quitAct = new QAction(tr("&Quit"), this);
+ quitAct->setMenuRole(QAction::QuitRole);
+ quitAct->setShortcut(tr("Ctrl+Q"));
+ quitAct->setStatusTip(tr("Bye"));
+ actions->insert("quit", quitAct);
+ connect(quitAct, SIGNAL(triggered()), this, SLOT(quit()));
+
+ siteAct = new QAction(tr("&Website"), this);
+ siteAct->setShortcut(QKeySequence::HelpContents);
+ siteAct->setStatusTip(tr("Minitube on the Web"));
+ actions->insert("site", siteAct);
+ connect(siteAct, SIGNAL(triggered()), this, SLOT(visitSite()));
+
+ donateAct = new QAction(tr("&Donate via PayPal"), this);
+ donateAct->setStatusTip(tr("Please support the continued development of %1").arg(Constants::APP_NAME));
+ actions->insert("donate", donateAct);
+ connect(donateAct, SIGNAL(triggered()), this, SLOT(donate()));
+
+ aboutAct = new QAction(tr("&About"), this);
+ aboutAct->setMenuRole(QAction::AboutRole);
+ aboutAct->setStatusTip(tr("Info about %1").arg(Constants::APP_NAME));
+ actions->insert("about", aboutAct);
+ connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));
+
+ searchFocusAct = new QAction(tr("&Search"), this);
+ searchFocusAct->setShortcut(QKeySequence::Find);
+ actions->insert("search", searchFocusAct);
+ connect(searchFocusAct, SIGNAL(triggered()), this, SLOT(searchFocus()));
+ addAction(searchFocusAct);
+
+ // common action properties
+ foreach (QAction *action, actions->values()) {
+ // never autorepeat.
+ // unexperienced users tend to keep keys pressed for a "long" time
+ action->setAutoRepeat(false);
+ action->setToolTip(action->statusTip());
+
+#ifdef Q_WS_MAC
+ // OSX does not use icons in menus
+ action->setIconVisibleInMenu(false);
+#endif
+
+ }
+
+}
+
+void MainWindow::createMenus() {
+
+ QMap<QString, QMenu*> *menus = The::globalMenus();
+
+ fileMenu = menuBar()->addMenu(tr("&Application"));
+ // menus->insert("file", fileMenu);
+ /*
+ fileMenu->addAction(settingsAct);
+ fileMenu->addSeparator();
+ */
+ fileMenu->addAction(quitAct);
+
+ playlistMenu = menuBar()->addMenu(tr("&Playlist"));
+ menus->insert("playlist", playlistMenu);
+ playlistMenu->addAction(removeAct);
+ playlistMenu->addSeparator();
+ playlistMenu->addAction(moveUpAct);
+ playlistMenu->addAction(moveDownAct);
+
+ viewMenu = menuBar()->addMenu(tr("&Video"));
+ menus->insert("video", viewMenu);
+ // viewMenu->addAction(backAct);
+ viewMenu->addAction(stopAct);
+ viewMenu->addAction(pauseAct);
+ viewMenu->addAction(skipAct);
+ viewMenu->addSeparator();
+ viewMenu->addAction(webPageAct);
+ viewMenu->addSeparator();
+ viewMenu->addAction(fullscreenAct);
+
+ helpMenu = menuBar()->addMenu(tr("&Help"));
+ helpMenu->addAction(siteAct);
+ helpMenu->addAction(donateAct);
+ helpMenu->addAction(aboutAct);
+}
+
+void MainWindow::createToolBars() {
+
+ setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
+
+ mainToolBar = new QToolBar(this);
+ mainToolBar->setFloatable(false);
+ mainToolBar->setMovable(false);
+
+ QFont smallerFont;
+ smallerFont.setPointSize(smallerFont.pointSize()*.85);
+ mainToolBar->setFont(smallerFont);
+
+ mainToolBar->setIconSize(QSize(32,32));
+ // mainToolBar->addAction(backAct);
+ mainToolBar->addAction(stopAct);
+ mainToolBar->addAction(pauseAct);
+ mainToolBar->addAction(skipAct);
+ mainToolBar->addAction(fullscreenAct);
+
+ seekSlider = new Phonon::SeekSlider(this);
+ seekSlider->setIconVisible(false);
+ // seekSlider->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
+ mainToolBar->addWidget(new Spacer(mainToolBar, seekSlider));
+
+ volumeSlider = new Phonon::VolumeSlider(this);
+ // this makes the volume slider smaller...
+ volumeSlider->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+ mainToolBar->addWidget(new Spacer(mainToolBar, volumeSlider));
+
+ // mainToolBar->addSeparator();
+ // mainToolBar->addAction(downloadAct);
+ // mainToolBar->addAction(webPageAct);
+
+ // toolbarSearch->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+ mainToolBar->addWidget(new Spacer(mainToolBar, toolbarSearch));
+
+ addToolBar(mainToolBar);
+}
+
+void MainWindow::createStatusBar() {
+ statusBar()->show();
+ statusBar()->setBackgroundRole(QPalette::Dark);
+ // statusBar()->setAutoFillBackground(true);
+}
+
+void MainWindow::readSettings() {
+ QSettings settings;
+ restoreGeometry(settings.value("geometry").toByteArray());
+}
+
+void MainWindow::writeSettings() {
+ QSettings settings;
+ settings.setValue("geometry", saveGeometry());
+}
+
+void MainWindow::goBack() {
+ if ( history->size() > 1 ) {
+ history->pop();
+ QWidget *widget = history->pop();
+ showWidget(widget);
+ }
+}
+
+void MainWindow::showWidget ( QWidget* widget ) {
+
+ // call hide method on the current view
+ View* oldView = dynamic_cast<View *> (views->currentWidget());
+ if (oldView != NULL) {
+ oldView->disappear();
+ }
+
+ // call show method on the new view
+ View* newView = dynamic_cast<View *> (widget);
+ if (newView != NULL) {
+ newView->appear();
+ QMap<QString,QVariant> metadata = newView->metadata();
+ QString windowTitle = metadata.value("title").toString();
+ if (windowTitle.length())
+ windowTitle += " - ";
+ setWindowTitle(windowTitle + Constants::APP_NAME);
+ setStatusTip(metadata.value("description").toString());
+ }
+
+ // backAct->setEnabled(history->size() > 1);
+ stopAct->setEnabled(widget == mediaView);
+ fullscreenAct->setEnabled(widget == mediaView);
+ webPageAct->setEnabled(widget == mediaView);
+ // settingsAct->setEnabled(widget != settingsView);
+ aboutAct->setEnabled(widget != aboutView);
+ // toolbar only for the mediaView
+
+ // This is cool on the Mac
+ // But does not respect layouts, maybe it's a Qt bug
+ // setUnifiedTitleAndToolBarOnMac(widget == mediaView);
+
+ mainToolBar->setVisible(widget == mediaView);
+
+ history->push(widget);
+ fadeInWidget(views->currentWidget(), widget);
+ views->setCurrentWidget(widget);
+
+}
+
+void MainWindow::fadeInWidget(QWidget *oldWidget, QWidget *newWidget) {
+ if (faderWidget) faderWidget->close();
+ if (!oldWidget || !newWidget || oldWidget == mediaView || newWidget == mediaView) return;
+ QPixmap frozenView = QPixmap::grabWidget(oldWidget);
+ faderWidget = new FaderWidget(newWidget);
+ faderWidget->start(frozenView);
+}
+
+void MainWindow::about() {
+ if (!aboutView) {
+ aboutView = new AboutView(this);
+ views->addWidget(aboutView);
+ }
+ showWidget(aboutView);
+}
+
+void MainWindow::visitSite() {
+ QUrl url(Constants::WEBSITE);
+ statusBar()->showMessage(QString(tr("Opening %1").arg(url.toString())));
+ QDesktopServices::openUrl(url);
+}
+
+void MainWindow::donate() {
+ QUrl url(QString(Constants::WEBSITE) + "#donate");
+ statusBar()->showMessage(QString(tr("Opening %1").arg(url.toString())));
+ QDesktopServices::openUrl(url);
+}
+
+void MainWindow::quit() {
+ writeSettings();
+ qApp->quit();
+}
+
+void MainWindow::closeEvent(QCloseEvent *event) {
+ quit();
+ QWidget::closeEvent(event);
+}
+
+void MainWindow::showSettings() {
+ if (!settingsView) {
+ settingsView = new SettingsView(this);
+ views->addWidget(settingsView);
+ }
+ showWidget(settingsView);
+}
+
+void MainWindow::showSearch() {
+ showWidget(searchView);
+}
+
+void MainWindow::showMedia(QString query) {
+ initPhonon();
+ mediaView->setMediaObject(mediaObject);
+ SearchParams *searchParams = new SearchParams();
+ searchParams->setKeywords(query);
+ mediaView->search(searchParams);
+ showWidget(mediaView);
+}
+
+void MainWindow::stateChanged(Phonon::State newState, Phonon::State /* oldState */) {
+
+ // qDebug() << "Phonon state: " << newState;
+
+ switch (newState) {
+
+ case Phonon::ErrorState:
+ if (mediaObject->errorType() == Phonon::FatalError) {
+ setStatusTip("Something went REALLY wrong: " + mediaObject->errorString());
+ } else {
+ setStatusTip("Something went wrong: " + mediaObject->errorString());
+ }
+ break;
+
+ case Phonon::PlayingState:
+ pauseAct->setEnabled(true);
+ pauseAct->setIcon(QtIconLoader::icon("media-pause", QIcon(":/images/pause.png")));
+ pauseAct->setText(tr("&Pause"));
+ pauseAct->setStatusTip(tr("Pause playback"));
+ skipAct->setEnabled(true);
+ break;
+
+ case Phonon::StoppedState:
+ pauseAct->setEnabled(false);
+ skipAct->setEnabled(false);
+ break;
+
+ case Phonon::PausedState:
+ skipAct->setEnabled(true);
+ pauseAct->setEnabled(true);
+ pauseAct->setIcon(QtIconLoader::icon("media-play", QIcon(":/images/play.png")));
+ pauseAct->setText(tr("&Play"));
+ pauseAct->setStatusTip(tr("Resume playback"));
+ break;
+
+ case Phonon::BufferingState:
+ case Phonon::LoadingState:
+ skipAct->setEnabled(true);
+ pauseAct->setEnabled(false);
+ break;
+
+ default:
+ ;
+ }
+}
+
+void MainWindow::stop() {
+ mediaView->stop();
+ showSearch();
+}
+
+void MainWindow::fullscreen() {
+ if (m_fullscreen) {
+ mediaView->exitFullscreen();
+ fullscreenAct->setShortcut(tr("Alt+Return"));
+ fullscreenAct->setText(tr("&Full Screen"));
+ stopAct->setShortcut(tr("Esc"));
+ } else {
+ mediaView->fullscreen();
+ stopAct->setShortcut(tr(""));
+ fullscreenAct->setShortcut(tr("Esc"));
+ fullscreenAct->setText(tr("Exit &Full Screen"));
+ }
+ m_fullscreen = !m_fullscreen;
+}
+
+void MainWindow::searchFocus() {
+ QWidget *view = views->currentWidget();
+ if (view == mediaView) {
+ toolbarSearch->setFocus();
+ }
+}
+
+void MainWindow::initPhonon() {
+ // Phonon initialization
+ if (mediaObject) delete mediaObject;
+ if (audioOutput) delete audioOutput;
+ mediaObject = new Phonon::MediaObject(this);
+ connect(mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
+ this, SLOT(stateChanged(Phonon::State, Phonon::State)));
+ seekSlider->setMediaObject(mediaObject);
+ audioOutput = new Phonon::AudioOutput(Phonon::VideoCategory, this);
+ volumeSlider->setAudioOutput(audioOutput);
+ Phonon::createPath(mediaObject, audioOutput);
+}
--- /dev/null
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QtGui>
+#include "faderwidget/FaderWidget.h"
+#include "searchlineedit.h"
+#include <phonon>
+#include "View.h"
+#include "SearchView.h"
+#include "MediaView.h"
+#include "SettingsView.h"
+#include "AboutView.h"
+
+class MainWindow : public QMainWindow {
+
+ Q_OBJECT
+
+public:
+ MainWindow();
+ ~MainWindow();
+
+protected:
+ void closeEvent(QCloseEvent *);
+
+private slots:
+ void fadeInWidget(QWidget *oldWidget, QWidget *newWidget);
+ void goBack();
+ void showSettings();
+ void showSearch();
+ void showMedia(QString query);
+ void visitSite();
+ void donate();
+ void about();
+ void quit();
+ void fullscreen();
+ void stop();
+ void stateChanged(Phonon::State newState, Phonon::State oldState);
+ void searchFocus();
+
+private:
+ void initPhonon();
+ void createActions();
+ void createMenus();
+ void createToolBars();
+ void createStatusBar();
+ void readSettings();
+ void writeSettings();
+ void showWidget(QWidget*);
+
+ // view mechanism
+ QPointer<FaderWidget> faderWidget;
+ QStackedWidget *views;
+ QStack<QWidget*> *history;
+
+ // view widgets
+ QWidget *searchView;
+ MediaView *mediaView;
+ QWidget *settingsView;
+ QWidget *aboutView;
+
+ // actions
+ QAction *addGadgetAct;
+ QAction *settingsAct;
+ QAction *backAct;
+ QAction *quitAct;
+ QAction *siteAct;
+ QAction *donateAct;
+ QAction *aboutAct;
+ QAction *searchFocusAct;
+
+ // media actions
+ QAction *skipAct;
+ QAction *pauseAct;
+ QAction *stopAct;
+ QAction *fullscreenAct;
+ QAction *webPageAct;
+ QAction *downloadAct;
+
+ // playlist actions
+ QAction *removeAct;
+ QAction *moveDownAct;
+ QAction *moveUpAct;
+ QAction *fetchMoreAct;
+
+ // menus
+ QMenu *fileMenu;
+ QMenu *viewMenu;
+ QMenu *playlistMenu;
+ QMenu *helpMenu;
+
+ // toolbar
+ QToolBar *mainToolBar;
+ SearchLineEdit *toolbarSearch;
+
+ // phonon
+ Phonon::SeekSlider *seekSlider;
+ Phonon::VolumeSlider *volumeSlider;
+ Phonon::MediaObject *mediaObject;
+ Phonon::AudioOutput *audioOutput;
+
+ bool m_fullscreen;
+};
+
+#endif
--- /dev/null
+#include "MediaView.h"
+#include "playlist/PrettyItemDelegate.h"
+#include "networkaccess.h"
+#include "videowidget.h"
+#include "minisplitter.h"
+
+namespace The {
+ QMap<QString, QAction*>* globalActions();
+ QMap<QString, QMenu*>* globalMenus();
+ QNetworkAccessManager* networkAccessManager();
+}
+
+MediaView::MediaView(QWidget *parent) : QWidget(parent) {
+
+ QBoxLayout *layout = new QHBoxLayout();
+ layout->setMargin(0);
+
+ splitter = new MiniSplitter(this);
+ splitter->setChildrenCollapsible(false);
+ // splitter->setBackgroundRole(QPalette::Text);
+ // splitter->setAutoFillBackground(true);
+
+ sortBar = new THBlackBar(this);
+ mostRelevantAction = new THAction(tr("Most relevant"), this);
+ connect(mostRelevantAction, SIGNAL(triggered()), this, SLOT(searchMostRelevant()), Qt::QueuedConnection);
+ sortBar->addAction(mostRelevantAction);
+ mostRecentAction = new THAction(tr("Most recent"), this);
+ connect(mostRecentAction, SIGNAL(triggered()), this, SLOT(searchMostRecent()), Qt::QueuedConnection);
+ sortBar->addAction(mostRecentAction);
+ mostViewedAction = new THAction(tr("Most viewed"), this);
+ connect(mostViewedAction, SIGNAL(triggered()), this, SLOT(searchMostViewed()), Qt::QueuedConnection);
+ sortBar->addAction(mostViewedAction);
+
+ listView = new QListView(this);
+ listView->setItemDelegate(new Playlist::PrettyItemDelegate(this));
+ listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
+
+ // dragndrop
+ listView->setDragEnabled(true);
+ listView->setAcceptDrops(true);
+ listView->setDropIndicatorShown(true);
+ listView->setDragDropMode(QAbstractItemView::InternalMove);
+
+ // cosmetics
+ listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
+ listView->setFrameShape( QFrame::NoFrame );
+ listView->setAttribute(Qt::WA_MacShowFocusRect, false);
+ listView->setMinimumSize(320,240);
+
+ // respond to the user doubleclicking a playlist item
+ connect(listView, SIGNAL(activated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &)));
+
+ listModel = new ListModel(this);
+ connect(listModel, SIGNAL(activeRowChanged(int)), this, SLOT(activeRowChanged(int)));
+ // needed to restore the selection after dragndrop
+ connect(listModel, SIGNAL(needSelectionFor(QList<Video*>)), this, SLOT(selectVideos(QList<Video*>)));
+ listView->setModel(listModel);
+
+ connect(listView->selectionModel(),
+ SIGNAL(selectionChanged ( const QItemSelection & , const QItemSelection & )),
+ this, SLOT(selectionChanged ( const QItemSelection & , const QItemSelection & )));
+
+ playlistWidget = new PlaylistWidget(this, sortBar, listView);
+
+ splitter->addWidget(playlistWidget);
+
+ videoWidget = new VideoWidget(this);
+ videoWidget->setMinimumSize(320,240);
+ splitter->addWidget(videoWidget);
+ // expand video by default
+ // splitter->setStretchFactor (1, 2);
+
+ layout->addWidget(splitter);
+ setLayout(layout);
+}
+
+MediaView::~MediaView() {
+
+}
+
+void MediaView::initialize() {
+ connect(videoWidget, SIGNAL(doubleClicked()), The::globalActions()->value("fullscreen"), SLOT(trigger()));
+ videoWidget->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(videoWidget, SIGNAL(customContextMenuRequested(QPoint)),
+ this, SLOT(showVideoContextMenu(QPoint)));
+}
+
+void MediaView::setMediaObject(Phonon::MediaObject *mediaObject) {
+ this->mediaObject = mediaObject;
+ Phonon::createPath(this->mediaObject, videoWidget);
+ // connect(mediaObject, SIGNAL(aboutToFinish()), this, SLOT(aboutToFinish()));
+ connect(mediaObject, SIGNAL(finished()), this, SLOT(skip()));
+ connect(mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
+ this, SLOT(stateChanged(Phonon::State, Phonon::State)));
+ connect(mediaObject, SIGNAL(currentSourceChanged(Phonon::MediaSource)),
+ this, SLOT(currentSourceChanged(Phonon::MediaSource)));
+ // connect(mediaObject, SIGNAL(tick(qint64)), this, SLOT(tick(qint64)));
+}
+
+void MediaView::search(SearchParams *searchParams) {
+ this->searchParams = searchParams;
+
+ // this implies that the enum and the bar action order is the same
+ sortBar->setCheckedAction(searchParams->sortBy()-1);
+
+ listModel->search(searchParams);
+ listView->setFocus();
+}
+
+void MediaView::disappear() {
+
+}
+
+void MediaView::stateChanged(Phonon::State newState, Phonon::State /* oldState */)
+{
+ switch (newState) {
+
+ case Phonon::ErrorState:
+ qDebug() << "Phonon error:" << mediaObject->errorString() << mediaObject->errorType();
+ // recover from errors by skipping to the next video
+ skip();
+ break;
+
+ case Phonon::PlayingState:
+ qDebug("playing");
+ break;
+
+ case Phonon::StoppedState:
+ qDebug("stopped");
+ // Play() has already been called when setting the source
+ // but Phonon on Linux needs a little more help to start playback
+ mediaObject->play();
+ break;
+
+ case Phonon::PausedState:
+ qDebug("paused");
+ break;
+
+ case Phonon::BufferingState:
+ qDebug("buffering");
+ break;
+
+ case Phonon::LoadingState:
+ qDebug("loading");
+ break;
+
+ default:
+ ;
+ }
+}
+
+void MediaView::pause() {
+ switch( mediaObject->state() ) {
+ case Phonon::PlayingState:
+ mediaObject->pause();
+ break;
+ default:
+ mediaObject->play();
+ break;
+ }
+}
+
+void MediaView::fullscreen() {
+
+#ifdef Q_WS_MAC
+ splitterState = splitter->saveState();
+ videoWidget->setParent(0);
+ videoWidget->showFullScreen();
+#else
+ videoWidget->setFullScreen(!videoWidget->isFullScreen());
+#endif
+
+}
+
+void MediaView::exitFullscreen() {
+
+#ifdef Q_WS_MAC
+ videoWidget->setParent(this);
+ splitter->addWidget(videoWidget);
+ videoWidget->showNormal();
+ splitter->restoreState(splitterState);
+#else
+ videoWidget->setFullScreen(false);
+#endif
+
+}
+
+void MediaView::stop() {
+ listModel->abortSearch();
+ mediaObject->stop();
+ mediaObject->clear();
+}
+
+void MediaView::activeRowChanged(int row) {
+ Video *video = listModel->videoAt(row);
+ if (!video) return;
+ QUrl streamUrl = video->streamUrl();
+ // qDebug() << "setCurrentSource" << streamUrl.toString();
+
+ // go!
+ mediaObject->setCurrentSource(streamUrl);
+ mediaObject->play();
+
+ // ensure we always have 10 videos ahead
+ listModel->searchNeeded();
+
+ // ensure active item is visible
+ QModelIndex index = listModel->index(row, 0, QModelIndex());
+ listView->scrollTo(index, QAbstractItemView::EnsureVisible);
+}
+
+void MediaView::itemActivated(const QModelIndex &index) {
+ if (listModel->rowExists(index.row()))
+ listModel->setActiveRow(index.row());
+ // the user doucleclicked on the "Search More" item
+ else listModel->searchMore();
+}
+
+void MediaView::aboutToFinish() {
+ /*
+ int nextRow = listModel->nextRow();
+ if (nextRow == -1) return;
+ Video* video = listModel->videoAt(nextRow);
+ QUrl streamUrl = video->streamUrl();
+ qDebug() << "Enqueing" << streamUrl;
+ mediaObject->enqueue(streamUrl);
+ */
+}
+
+void MediaView::currentSourceChanged(const Phonon::MediaSource source) {
+ qDebug() << "Source changed:" << source.url();
+}
+
+void MediaView::skip() {
+ int nextRow = listModel->nextRow();
+ if (nextRow == -1) return;
+ listModel->setActiveRow(nextRow);
+}
+
+void MediaView::openWebPage() {
+ Video* video = listModel->activeVideo();
+ if (!video) return;
+ mediaObject->pause();
+ QDesktopServices::openUrl(video->webpage());
+}
+
+void MediaView::removeSelected() {
+ if (!listView->selectionModel()->hasSelection()) return;
+ QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
+ listModel->removeIndexes(indexes);
+}
+
+void MediaView::selectVideos(QList<Video*> videos) {
+ foreach (Video *video, videos) {
+ QModelIndex index = listModel->indexForVideo(video);
+ listView->selectionModel()->select(index, QItemSelectionModel::Select);
+ listView->scrollTo(index, QAbstractItemView::EnsureVisible);
+ }
+}
+
+void MediaView::selectionChanged(const QItemSelection & /*selected*/, const QItemSelection & /*deselected*/) {
+ const bool gotSelection = listView->selectionModel()->hasSelection();
+ The::globalActions()->value("remove")->setEnabled(gotSelection);
+ The::globalActions()->value("moveUp")->setEnabled(gotSelection);
+ The::globalActions()->value("moveDown")->setEnabled(gotSelection);
+}
+
+void MediaView::moveUpSelected() {
+ if (!listView->selectionModel()->hasSelection()) return;
+ QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
+ listModel->move(indexes, true);
+}
+
+void MediaView::moveDownSelected() {
+ if (!listView->selectionModel()->hasSelection()) return;
+ QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
+ listModel->move(indexes, false);
+}
+
+void MediaView::showVideoContextMenu(QPoint point) {
+ The::globalMenus()->value("video")->popup(videoWidget->mapToGlobal(point));
+}
+
+void MediaView::searchMostRelevant() {
+ searchParams->setSortBy(SearchParams::SortByRelevance);
+ search(searchParams);
+}
+
+void MediaView::searchMostRecent() {
+ searchParams->setSortBy(SearchParams::SortByNewest);
+ search(searchParams);
+}
+
+void MediaView::searchMostViewed() {
+ searchParams->setSortBy(SearchParams::SortByViewCount);
+ search(searchParams);
+}
--- /dev/null
+#ifndef __MEDIAVIEW_H__
+#define __MEDIAVIEW_H__
+
+#include <QtGui>
+#include <QtNetwork>
+#include <phonon>
+#include "View.h"
+#include "ListModel.h"
+#include "thaction.h"
+#include "thblackbar.h"
+#include "searchparams.h"
+#include "playlistwidget.h"
+
+class MediaView : public QWidget, public View {
+ Q_OBJECT
+
+public:
+ MediaView(QWidget *parent);
+ ~MediaView();
+ void initialize();
+
+ // View
+ void appear() {}
+ void disappear();
+ QMap<QString, QVariant> metadata() {
+ QMap<QString, QVariant> metadata;
+ if (searchParams) {
+ metadata.insert("title", searchParams->keywords());
+ metadata.insert("description", tr("You're watching \"%1\"").arg(searchParams->keywords()));
+ }
+ return metadata;
+ }
+
+ void setMediaObject(Phonon::MediaObject *mediaObject);
+
+public slots:
+ void search(SearchParams *searchParams);
+ void pause();
+ void stop();
+ void skip();
+ void fullscreen();
+ void exitFullscreen();
+ void openWebPage();
+ void removeSelected();
+ void moveUpSelected();
+ void moveDownSelected();
+
+private slots:
+ // list/model
+ void itemActivated(const QModelIndex &index);
+ void selectionChanged (const QItemSelection & selected, const QItemSelection & deselected);
+ void activeRowChanged(int);
+ void selectVideos(QList<Video*> videos);
+ // phonon
+ void stateChanged(Phonon::State newState, Phonon::State oldState);
+ void aboutToFinish();
+ void currentSourceChanged(const Phonon::MediaSource source);
+ void showVideoContextMenu(QPoint point);
+ // bar
+ void searchMostRelevant();
+ void searchMostRecent();
+ void searchMostViewed();
+
+private:
+
+ SearchParams *searchParams;
+
+ QSplitter *splitter;
+ QByteArray splitterState;
+
+ PlaylistWidget *playlistWidget;
+ QListView *listView;
+ ListModel *listModel;
+
+ // sortBar
+ THBlackBar *sortBar;
+ THAction *mostRelevantAction;
+ THAction *mostRecentAction;
+ THAction *mostViewedAction;
+
+ // phonon
+ Phonon::MediaObject *mediaObject;
+ Phonon::VideoWidget *videoWidget;
+
+ QNetworkReply *networkReply;
+
+};
+
+#endif // __MEDIAVIEW_H__
--- /dev/null
+#include "SearchView.h"
+#include "Constants.h"
+
+static const QString recentKeywordsKey = "recentKeywords";
+static const int PADDING = 40;
+
+SearchView::SearchView(QWidget *parent) : QWidget(parent) {
+
+ QFont biggerFont;
+ biggerFont.setPointSize(biggerFont.pointSize()*1.5);
+
+ QFont smallerFont;
+ smallerFont.setPointSize(smallerFont.pointSize()*.85);
+ smallerFont.setBold(true);
+
+ QVBoxLayout *mainLayout = new QVBoxLayout();
+ mainLayout->setMargin(0);
+
+ // hidden message widget
+ message = new QLabel(this);
+ message->hide();
+ mainLayout->addWidget(message);
+
+ QVBoxLayout *layout = new QVBoxLayout();
+ layout->setMargin(PADDING);
+ mainLayout->addLayout(layout);
+
+ QLabel *welcomeLabel =
+ new QLabel("<h1>" +
+ tr("Welcome to <a href='%1'>%2</a>,").arg(Constants::WEBSITE, Constants::APP_NAME)
+ + "</h1>", this);
+ welcomeLabel->setOpenExternalLinks(true);
+ layout->addWidget(welcomeLabel);
+
+ layout->addSpacing(PADDING);
+
+ QLabel *tipLabel = new QLabel(tr("Enter a keyword to start watching videos."), this);
+ tipLabel->setFont(biggerFont);
+ layout->addWidget(tipLabel);
+
+ QHBoxLayout *searchLayout = new QHBoxLayout();
+
+ queryEdit = new SearchLineEdit(this);
+ queryEdit->setFont(biggerFont);
+ queryEdit->setMinimumWidth(300);
+ queryEdit->sizeHint();
+ queryEdit->setFocus(Qt::OtherFocusReason);
+ // connect(queryEdit, SIGNAL(returnPressed()), this, SLOT(watch()));
+ connect(queryEdit, SIGNAL(search(const QString&)), this, SLOT(watch(const QString&)));
+ searchLayout->addWidget(queryEdit);
+
+ searchLayout->addSpacing(10);
+
+ QPushButton *watchButton = new QPushButton(tr("Watch"), this);
+ watchButton->setDefault(true);
+ connect(watchButton, SIGNAL(clicked()), this, SLOT(watch()));
+ searchLayout->addWidget(watchButton);
+
+ searchLayout->addStretch();
+
+ layout->addItem(searchLayout);
+
+ layout->addSpacing(PADDING);
+
+ QHBoxLayout *otherLayout = new QHBoxLayout();
+
+ recentKeywordsLayout = new QVBoxLayout();
+ recentKeywordsLayout->setAlignment(Qt::AlignTop);
+ QLabel *label = new QLabel(tr("Recent keywords").toUpper(), this);
+ label->setForegroundRole(QPalette::Dark);
+ label->setFont(smallerFont);
+ recentKeywordsLayout->addWidget(label);
+
+ otherLayout->addLayout(recentKeywordsLayout);
+
+ layout->addLayout(otherLayout);
+
+ layout->addStretch();
+
+ setLayout(mainLayout);
+
+ // watchButton->setMinimumHeight(queryEdit->height());
+
+ updateChecker = 0;
+ checkForUpdate();
+}
+
+void SearchView::paintEvent(QPaintEvent * /*event*/) {
+
+ QPainter painter(this);
+
+#ifdef Q_WS_MAC
+ QLinearGradient linearGrad(0, 0, 0, height());
+ QPalette palette;
+ linearGrad.setColorAt(0, palette.color(QPalette::Light));
+ linearGrad.setColorAt(1, palette.color(QPalette::Midlight));
+ painter.fillRect(0, 0, width(), height(), QBrush(linearGrad));
+#endif
+
+ QPixmap watermark = QPixmap(":/images/app.png");
+ painter.setOpacity(.25);
+ painter.drawPixmap(width() - watermark.width() - PADDING,
+ height() - watermark.height() - PADDING,
+ watermark.width(),
+ watermark.height(),
+ watermark);
+}
+
+void SearchView::updateRecentKeywords() {
+
+ // cleanup
+ QLayoutItem *item;
+ while ((item = recentKeywordsLayout->takeAt(1)) != 0) {
+ item->widget()->close();
+ delete item;
+ }
+
+ // load
+ QSettings settings;
+ QStringList keywords = settings.value(recentKeywordsKey).toStringList();
+ foreach (QString keyword, keywords) {
+ QLabel *itemLabel = new QLabel("<a href=\"" + keyword
+ + "\" style=\"color:palette(text); text-decoration:none\">"
+ + keyword + "</a>", this);
+ connect(itemLabel, SIGNAL(linkActivated(QString)), this, SLOT(watch(QString)));
+ recentKeywordsLayout->addWidget(itemLabel);
+ }
+
+}
+
+void SearchView::watch() {
+ QString query = queryEdit->text().trimmed();
+ watch(query);
+}
+
+void SearchView::watch(QString query) {
+
+ // check for empty query
+ if (query.length() == 0) {
+ queryEdit->setFocus(Qt::OtherFocusReason);
+ return;
+ }
+
+ // save keyword
+ QSettings settings;
+ QStringList keywords = settings.value(recentKeywordsKey).toStringList();
+ keywords.removeAll(query);
+ keywords.prepend(query);
+ while (keywords.size() > 10)
+ keywords.removeLast();
+ settings.setValue(recentKeywordsKey, keywords);
+
+ // go!
+ emit search(query);
+}
+
+void SearchView::checkForUpdate() {
+ static const QString updateCheckKey = "updateCheck";
+
+ // check every 24h
+ QSettings settings;
+ uint unixTime = QDateTime::currentDateTime().toTime_t();
+ int lastCheck = settings.value(updateCheckKey).toInt();
+ int secondsSinceLastCheck = unixTime - lastCheck;
+ // qDebug() << "secondsSinceLastCheck" << unixTime << lastCheck << secondsSinceLastCheck;
+ if (secondsSinceLastCheck < 86400) return;
+
+ // check it out
+ if (updateChecker) delete updateChecker;
+ updateChecker = new UpdateChecker();
+ connect(updateChecker, SIGNAL(newVersion(QString)),
+ this, SLOT(gotNewVersion(QString)));
+ updateChecker->checkForUpdate();
+ settings.setValue(updateCheckKey, unixTime);
+
+}
+
+void SearchView::gotNewVersion(QString version) {
+ message->setText(
+ tr("A new version of %1 is available. Please <a href='%2'>update to version %3</a>")
+ .arg(
+ Constants::APP_NAME,
+ QString(Constants::WEBSITE).append("#download"),
+ version)
+ );
+ message->setOpenExternalLinks(true);
+ message->setMargin(10);
+ message->setBackgroundRole(QPalette::ToolTipBase);
+ message->setForegroundRole(QPalette::ToolTipText);
+ message->setAutoFillBackground(true);
+ message->show();
+ if (updateChecker) delete updateChecker;
+}
--- /dev/null
+#ifndef __SEARCHVIEW_H__
+#define __SEARCHVIEW_H__
+
+#include <QtGui>
+#include "View.h"
+#include "searchlineedit.h"
+#include "updatechecker.h"
+
+class SearchView : public QWidget, public View {
+
+ Q_OBJECT
+
+public:
+ SearchView(QWidget *parent);
+
+ void appear() {
+ updateRecentKeywords();
+ queryEdit->clear();
+ queryEdit->setFocus(Qt::OtherFocusReason);
+ }
+
+ void disappear() {}
+
+ QMap<QString, QVariant> metadata() {
+ QMap<QString, QVariant> metadata;
+ metadata.insert("title", "");
+ metadata.insert("description", tr("Make yourself comfortable"));
+ return metadata;
+ }
+
+public slots:
+ void watch(QString query);
+ void gotNewVersion(QString version);
+
+protected:
+ void paintEvent(QPaintEvent *);
+
+signals:
+ void search(QString query);
+
+private slots:
+ void watch();
+
+private:
+ void updateRecentKeywords();
+ void checkForUpdate();
+
+ SearchLineEdit *queryEdit;
+ QVBoxLayout *recentKeywordsLayout;
+ QLabel *message;
+
+ UpdateChecker *updateChecker;
+
+
+};
+
+#endif // __SEARCHVIEW_H__
--- /dev/null
+#include "SettingsView.h"
+
+SettingsView::SettingsView( QWidget *parent ) : QWidget(parent) {
+
+ QSettings settings;
+
+}
+
+void SettingsView::storeSettings() {
+
+}
--- /dev/null
+#ifndef SETTINGSVIEW_H
+#define SETTINGSVIEW_H
+
+#include <QtGui>
+#include "View.h"
+
+class SettingsView : public QWidget, public View {
+
+ Q_OBJECT
+
+public:
+ SettingsView(QWidget *parent);
+ void appear() {}
+ void disappear() {}
+ QMap<QString, QVariant> metadata() {
+ QMap<QString, QVariant> metadata;
+ metadata.insert("title", tr("Preferences"));
+ metadata.insert("description", tr(""));
+ return metadata;
+ }
+
+private slots:
+ void storeSettings();
+
+private:
+ QLineEdit *urlEdit;
+ QLabel *urlLabel;
+
+};
+#endif
--- /dev/null
+#ifndef VIEW_H
+#define VIEW_H
+
+class View {
+
+ public:
+ virtual QMap<QString, QVariant> metadata() = 0;
+ virtual void appear() = 0;
+ virtual void disappear() = 0;
+
+};
+
+#endif // VIEW_H
--- /dev/null
+#include "FaderWidget.h"
+
+// http://labs.trolltech.com/blogs/2007/08/21/fade-effects-a-blast-from-the-past/
+
+FaderWidget::FaderWidget(QWidget *parent) : QWidget(parent) {
+ timeLine = new QTimeLine(333, this);
+ timeLine->setFrameRange(1000, 0);
+ connect(timeLine, SIGNAL(frameChanged(int)), this, SLOT(update()));
+ setAttribute(Qt::WA_DeleteOnClose);
+ resize(parent->size());
+}
+
+void FaderWidget::start(QPixmap frozenView) {
+ this->frozenView = frozenView;
+ timeLine->start();
+ show();
+}
+
+void FaderWidget::paintEvent(QPaintEvent *) {
+ const qreal opacity = timeLine->currentFrame() / 1000.;
+ QPainter painter(this);
+ painter.setOpacity(opacity);
+ painter.drawPixmap(rect(), frozenView);
+
+ if (opacity <= 0.)
+ close();
+
+}
--- /dev/null
+#ifndef FADERWIDGET_H
+#define FADERWIDGET_H
+
+#include <QtGui>
+
+class FaderWidget : public QWidget {
+
+ Q_OBJECT
+ Q_PROPERTY(int fadeDuration READ fadeDuration WRITE setFadeDuration)
+
+public:
+
+ FaderWidget(QWidget *parent);
+
+ int fadeDuration() const {
+ return timeLine->duration();
+ }
+ void setFadeDuration(int milliseconds) {
+ timeLine->setDuration(milliseconds);
+ }
+ void start(QPixmap frozenView);
+
+protected:
+ void paintEvent(QPaintEvent *event);
+
+private:
+ QTimeLine *timeLine;
+ QPixmap frozenView;
+
+};
+
+#endif
--- /dev/null
+#ifndef GLOBAL_H
+#define GLOBAL_H
+
+#include <QtGui>
+#include <QNetworkAccessManager>
+#include "networkaccess.h"
+
+namespace The {
+
+ static QMap<QString, QAction*> *g_actions = 0;
+
+ QMap<QString, QAction*>* globalActions() {
+ if (!g_actions)
+ g_actions = new QMap<QString, QAction*>;
+ return g_actions;
+ }
+
+ static QMap<QString, QMenu*> *g_menus = 0;
+
+ QMap<QString, QMenu*>* globalMenus() {
+ if (!g_menus)
+ g_menus = new QMap<QString, QMenu*>;
+ return g_menus;
+ }
+
+ static QNetworkAccessManager *nam = 0;
+
+ QNetworkAccessManager* networkAccessManager() {
+ if (!nam) {
+ nam = new QNetworkAccessManager();
+
+ // A simple disk based cache
+ /*
+ QNetworkDiskCache *cache = new QNetworkDiskCache();
+ QString cacheLocation = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
+ qDebug() << cacheLocation;
+ cache->setCacheDirectory(cacheLocation);
+ nam->setCache(cache);
+ */
+ }
+ return nam;
+ }
+
+ static NetworkAccess *g_http = 0;
+ NetworkAccess* http() {
+ if (!g_http)
+ g_http = new NetworkAccess();
+ return g_http;
+ }
+
+}
+
+#endif // GLOBAL_H
--- /dev/null
+9
+
+dir
+895
+svn://labs.trolltech.com/svn/iconloader
+svn://labs.trolltech.com/svn
+
+
+
+2009-03-13T15:08:29.000000Z
+894
+jbache
+
+
+svn:special svn:externals svn:needs-lock
+
+
+
+
+
+
+
+
+
+
+
+68458f37-fe29-0410-bab8-8310f566acca
+\f
+qticonloader.cpp
+file
+
+
+
+
+2009-04-20T14:58:29.000000Z
+d13de72db2a7d48d969399b35940fadf
+2009-03-13T15:08:29.000000Z
+894
+jbache
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+12929
+\f
+qticonloader.h
+file
+
+
+
+
+2009-04-20T14:58:29.000000Z
+5e81d9e5caa3af56d78ec48c352a8ced
+2009-03-13T14:15:44.000000Z
+893
+jbache
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+2158
+\f
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qticonloader.h"
+#include <QtGui/QPixmapCache>
+
+#include <QtCore/QList>
+#include <QtCore/QHash>
+#include <QtCore/QDir>
+#include <QtCore/QString>
+#include <QtCore/QLibrary>
+#include <QtCore/QSettings>
+#include <QtCore/QTextStream>
+
+#ifdef Q_WS_X11
+
+class QIconTheme
+{
+public:
+ QIconTheme(QHash <int, QString> dirList, QStringList parents) :
+ _dirList(dirList), _parents(parents), _valid(true){ }
+ QIconTheme() : _valid(false){ }
+ QHash <int, QString> dirList() {return _dirList;}
+ QStringList parents() {return _parents;}
+ bool isValid() {return _valid;}
+
+private:
+ QHash <int, QString> _dirList;
+ QStringList _parents;
+ bool _valid;
+};
+
+class QtIconLoaderImplementation
+{
+public:
+ QtIconLoaderImplementation();
+ QPixmap findIcon(int size, const QString &name) const;
+
+private:
+ QIconTheme parseIndexFile(const QString &themeName) const;
+ void lookupIconTheme() const;
+ QPixmap findIconHelper(int size,
+ const QString &themeName,
+ const QString &iconName,
+ QStringList &visited) const;
+ mutable QString themeName;
+ mutable QStringList iconDirs;
+ mutable QHash <QString, QIconTheme> themeList;
+};
+
+Q_GLOBAL_STATIC(QtIconLoaderImplementation, iconLoaderInstance)
+#endif
+
+/*!
+
+ Returns the standard icon for the given icon /a name
+ as specified in the freedesktop icon spec
+ http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
+
+ /a fallback is an optional argument to specify the icon to be used if
+ no icon is found on the platform. This is particularily useful for
+ crossplatform code.
+
+*/
+QIcon QtIconLoader::icon(const QString &name, const QIcon &fallback)
+{
+ QIcon icon;
+#ifdef Q_WS_X11
+ QString pngExtension(QLatin1String(".png"));
+ QList<int> iconSizes;
+ iconSizes << 16 << 24 << 32 << 48 << 64;
+ Q_FOREACH (int size, iconSizes) {
+ icon.addPixmap(iconLoaderInstance()->findIcon(size, name + pngExtension));
+ }
+#endif
+ if (icon.isNull())
+ icon = fallback;
+ Q_UNUSED(name);
+ return icon;
+}
+
+#ifdef Q_WS_X11
+
+QtIconLoaderImplementation::QtIconLoaderImplementation()
+{
+ lookupIconTheme();
+}
+
+extern "C" {
+ struct GConfClient;
+ struct GError;
+ typedef void (*Ptr_g_type_init)();
+ typedef GConfClient* (*Ptr_gconf_client_get_default)();
+ typedef char* (*Ptr_gconf_client_get_string)(GConfClient*, const char*, GError **);
+ typedef void (*Ptr_g_object_unref)(void *);
+ typedef void (*Ptr_g_error_free)(GError *);
+ typedef void (*Ptr_g_free)(void*);
+ static Ptr_g_type_init p_g_type_init = 0;
+ static Ptr_gconf_client_get_default p_gconf_client_get_default = 0;
+ static Ptr_gconf_client_get_string p_gconf_client_get_string = 0;
+ static Ptr_g_object_unref p_g_object_unref = 0;
+ static Ptr_g_error_free p_g_error_free = 0;
+ static Ptr_g_free p_g_free = 0;
+}
+
+
+static int kdeVersion()
+{
+ static int version = qgetenv("KDE_SESSION_VERSION").toInt();
+ return version;
+}
+
+static QString kdeHome()
+{
+ static QString kdeHomePath;
+ if (kdeHomePath.isEmpty()) {
+ kdeHomePath = QFile::decodeName(qgetenv("KDEHOME"));
+ if (kdeHomePath.isEmpty()) {
+ int kdeSessionVersion = kdeVersion();
+ QDir homeDir(QDir::homePath());
+ QString kdeConfDir(QLatin1String("/.kde"));
+ if (4 == kdeSessionVersion && homeDir.exists(QLatin1String(".kde4")))
+ kdeConfDir = QLatin1String("/.kde4");
+ kdeHomePath = QDir::homePath() + kdeConfDir;
+ }
+ }
+ return kdeHomePath;
+}
+
+void QtIconLoaderImplementation::lookupIconTheme() const
+{
+
+#ifdef Q_WS_X11
+ QString dataDirs = QFile::decodeName(getenv("XDG_DATA_DIRS"));
+ if (dataDirs.isEmpty())
+ dataDirs = QLatin1String("/usr/local/share/:/usr/share/");
+
+ dataDirs.prepend(QDir::homePath() + QLatin1String("/:"));
+ iconDirs = dataDirs.split(QLatin1Char(':'));
+
+ // If we are running GNOME we resolve and use GConf. In all other
+ // cases we currently use the KDE icon theme
+
+ if (qgetenv("DESKTOP_SESSION") == "gnome" ||
+ !qgetenv("GNOME_DESKTOP_SESSION_ID").isEmpty()) {
+
+ if (themeName.isEmpty()) {
+ // Resolve glib and gconf
+
+ p_g_type_init = (Ptr_g_type_init)QLibrary::resolve(QLatin1String("gobject-2.0"), 0, "g_type_init");
+ p_gconf_client_get_default = (Ptr_gconf_client_get_default)QLibrary::resolve(QLatin1String("gconf-2"), 4, "gconf_client_get_default");
+ p_gconf_client_get_string = (Ptr_gconf_client_get_string)QLibrary::resolve(QLatin1String("gconf-2"), 4, "gconf_client_get_string");
+ p_g_object_unref = (Ptr_g_object_unref)QLibrary::resolve(QLatin1String("gobject-2.0"), 0, "g_object_unref");
+ p_g_error_free = (Ptr_g_error_free)QLibrary::resolve(QLatin1String("glib-2.0"), 0, "g_error_free");
+ p_g_free = (Ptr_g_free)QLibrary::resolve(QLatin1String("glib-2.0"), 0, "g_free");
+
+ if (p_g_type_init && p_gconf_client_get_default &&
+ p_gconf_client_get_string && p_g_object_unref &&
+ p_g_error_free && p_g_free) {
+
+ p_g_type_init();
+ GConfClient* client = p_gconf_client_get_default();
+ GError *err = 0;
+
+ char *str = p_gconf_client_get_string(client, "/desktop/gnome/interface/icon_theme", &err);
+ if (!err) {
+ themeName = QString::fromUtf8(str);
+ p_g_free(str);
+ }
+
+ p_g_object_unref(client);
+ if (err)
+ p_g_error_free (err);
+ }
+ if (themeName.isEmpty())
+ themeName = QLatin1String("gnome");
+ }
+
+ if (!themeName.isEmpty())
+ return;
+ }
+
+ // KDE (and others)
+ if (dataDirs.isEmpty())
+ dataDirs = QLatin1String("/usr/local/share/:/usr/share/");
+
+ dataDirs += QLatin1Char(':') + kdeHome() + QLatin1String("/share");
+ dataDirs.prepend(QDir::homePath() + QLatin1String("/:"));
+ QStringList kdeDirs = QFile::decodeName(getenv("KDEDIRS")).split(QLatin1Char(':'));
+ Q_FOREACH (const QString dirName, kdeDirs)
+ dataDirs.append(QLatin1Char(':') + dirName + QLatin1String("/share"));
+ iconDirs = dataDirs.split(QLatin1Char(':'));
+
+ QFileInfo fileInfo(QLatin1String("/usr/share/icons/default.kde"));
+ QDir dir(fileInfo.canonicalFilePath());
+ QString kdeDefault = kdeVersion() >= 4 ? QString::fromLatin1("oxygen") : QString::fromLatin1("crystalsvg");
+ QString defaultTheme = fileInfo.exists() ? dir.dirName() : kdeDefault;
+ QSettings settings(kdeHome() + QLatin1String("/share/config/kdeglobals"), QSettings::IniFormat);
+ settings.beginGroup(QLatin1String("Icons"));
+ themeName = settings.value(QLatin1String("Theme"), defaultTheme).toString();
+#endif
+}
+
+QIconTheme QtIconLoaderImplementation::parseIndexFile(const QString &themeName) const
+{
+ QIconTheme theme;
+ QFile themeIndex;
+ QStringList parents;
+ QHash <int, QString> dirList;
+
+ for ( int i = 0 ; i < iconDirs.size() && !themeIndex.exists() ; ++i) {
+ const QString &contentDir = QLatin1String(iconDirs[i].startsWith(QDir::homePath()) ? "/.icons/" : "/icons/");
+ themeIndex.setFileName(iconDirs[i] + contentDir + themeName + QLatin1String("/index.theme"));
+ }
+
+ if (themeIndex.exists()) {
+ QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat);
+ Q_FOREACH (const QString &key, indexReader.allKeys()) {
+ if (key.endsWith("/Size")) {
+ if (int size = indexReader.value(key).toInt())
+ dirList.insertMulti(size, key.left(key.size() - 5));
+ }
+ }
+
+ // Parent themes provide fallbacks for missing icons
+ parents = indexReader.value(QLatin1String("Icon Theme/Inherits")).toString().split(QLatin1Char(','));
+ }
+
+ if (kdeVersion() >= 3) {
+ QFileInfo fileInfo(QLatin1String("/usr/share/icons/default.kde"));
+ QDir dir(fileInfo.canonicalFilePath());
+ QString defaultKDETheme = dir.exists() ? dir.dirName() : kdeVersion() == 3 ?
+ QString::fromLatin1("crystalsvg") : QString::fromLatin1("oxygen");
+ if (!parents.contains(defaultKDETheme) && themeName != defaultKDETheme)
+ parents.append(defaultKDETheme);
+ } else if (parents.isEmpty() && themeName != QLatin1String("hicolor")) {
+ parents.append(QLatin1String("hicolor"));
+ }
+
+ theme = QIconTheme(dirList, parents);
+ return theme;
+}
+
+QPixmap QtIconLoaderImplementation::findIconHelper(int size, const QString &themeName,
+ const QString &iconName, QStringList &visited) const
+{
+ QPixmap pixmap;
+
+ if (!themeName.isEmpty()) {
+ visited << themeName;
+ QIconTheme theme = themeList.value(themeName);
+
+ if (!theme.isValid()) {
+ theme = parseIndexFile(themeName);
+ themeList.insert(themeName, theme);
+ }
+
+ if (!theme.isValid())
+ return QPixmap();
+
+ QList <QString> subDirs = theme.dirList().values(size);
+
+ for ( int i = 0 ; i < iconDirs.size() ; ++i) {
+ for ( int j = 0 ; j < subDirs.size() ; ++j) {
+ QString contentDir = (iconDirs[i].startsWith(QDir::homePath())) ?
+ QLatin1String("/.icons/") : QLatin1String("/icons/");
+ QString fileName = iconDirs[i] + contentDir + themeName + QLatin1Char('/') + subDirs[j] + QLatin1Char('/') + iconName;
+ QFile file(fileName);
+ if (file.exists())
+ pixmap.load(fileName);
+ if (!pixmap.isNull())
+ break;
+ }
+ }
+
+ if (pixmap.isNull()) {
+ QStringList parents = theme.parents();
+ //search recursively through inherited themes
+ for (int i = 0 ; pixmap.isNull() && i < parents.size() ; ++i) {
+ QString parentTheme = parents[i].trimmed();
+ if (!visited.contains(parentTheme)) //guard against endless recursion
+ pixmap = findIconHelper(size, parentTheme, iconName, visited);
+ }
+ }
+ }
+ return pixmap;
+}
+
+QPixmap QtIconLoaderImplementation::findIcon(int size, const QString &name) const
+{
+ QPixmap pixmap;
+ QString pixmapName = QLatin1String("$qt") + name + QString::number(size);
+ if (QPixmapCache::find(pixmapName, pixmap))
+ return pixmap;
+
+ if (!themeName.isEmpty()) {
+ QStringList visited;
+ pixmap = findIconHelper(size, themeName, name, visited);
+ }
+ QPixmapCache::insert(pixmapName, pixmap);
+ return pixmap;
+}
+#endif //Q_WS_X11
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QTICONLOADER_H
+#define QTICONLOADER_H
+
+#include <QtGui/QIcon>
+
+// This is the QtIconLoader
+// Version 0.1
+//
+
+class QtIconLoader
+{
+public:
+ static QIcon icon(const QString &name, const QIcon &fallback = QIcon());
+};
+
+#endif // QTICONLOADER_H
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qticonloader.h"
+#include <QtGui/QPixmapCache>
+
+#include <QtCore/QList>
+#include <QtCore/QHash>
+#include <QtCore/QDir>
+#include <QtCore/QString>
+#include <QtCore/QLibrary>
+#include <QtCore/QSettings>
+#include <QtCore/QTextStream>
+
+#ifdef Q_WS_X11
+
+class QIconTheme
+{
+public:
+ QIconTheme(QHash <int, QString> dirList, QStringList parents) :
+ _dirList(dirList), _parents(parents), _valid(true){ }
+ QIconTheme() : _valid(false){ }
+ QHash <int, QString> dirList() {return _dirList;}
+ QStringList parents() {return _parents;}
+ bool isValid() {return _valid;}
+
+private:
+ QHash <int, QString> _dirList;
+ QStringList _parents;
+ bool _valid;
+};
+
+class QtIconLoaderImplementation
+{
+public:
+ QtIconLoaderImplementation();
+ QPixmap findIcon(int size, const QString &name) const;
+
+private:
+ QIconTheme parseIndexFile(const QString &themeName) const;
+ void lookupIconTheme() const;
+ QPixmap findIconHelper(int size,
+ const QString &themeName,
+ const QString &iconName,
+ QStringList &visited) const;
+ mutable QString themeName;
+ mutable QStringList iconDirs;
+ mutable QHash <QString, QIconTheme> themeList;
+};
+
+Q_GLOBAL_STATIC(QtIconLoaderImplementation, iconLoaderInstance)
+#endif
+
+/*!
+
+ Returns the standard icon for the given icon /a name
+ as specified in the freedesktop icon spec
+ http://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
+
+ /a fallback is an optional argument to specify the icon to be used if
+ no icon is found on the platform. This is particularily useful for
+ crossplatform code.
+
+*/
+QIcon QtIconLoader::icon(const QString &name, const QIcon &fallback)
+{
+ QIcon icon;
+#ifdef Q_WS_X11
+ QString pngExtension(QLatin1String(".png"));
+ QList<int> iconSizes;
+ iconSizes << 16 << 24 << 32 << 48 << 64;
+ Q_FOREACH (int size, iconSizes) {
+ icon.addPixmap(iconLoaderInstance()->findIcon(size, name + pngExtension));
+ }
+#endif
+ if (icon.isNull())
+ icon = fallback;
+ Q_UNUSED(name);
+ return icon;
+}
+
+#ifdef Q_WS_X11
+
+QtIconLoaderImplementation::QtIconLoaderImplementation()
+{
+ lookupIconTheme();
+}
+
+extern "C" {
+ struct GConfClient;
+ struct GError;
+ typedef void (*Ptr_g_type_init)();
+ typedef GConfClient* (*Ptr_gconf_client_get_default)();
+ typedef char* (*Ptr_gconf_client_get_string)(GConfClient*, const char*, GError **);
+ typedef void (*Ptr_g_object_unref)(void *);
+ typedef void (*Ptr_g_error_free)(GError *);
+ typedef void (*Ptr_g_free)(void*);
+ static Ptr_g_type_init p_g_type_init = 0;
+ static Ptr_gconf_client_get_default p_gconf_client_get_default = 0;
+ static Ptr_gconf_client_get_string p_gconf_client_get_string = 0;
+ static Ptr_g_object_unref p_g_object_unref = 0;
+ static Ptr_g_error_free p_g_error_free = 0;
+ static Ptr_g_free p_g_free = 0;
+}
+
+
+static int kdeVersion()
+{
+ static int version = qgetenv("KDE_SESSION_VERSION").toInt();
+ return version;
+}
+
+static QString kdeHome()
+{
+ static QString kdeHomePath;
+ if (kdeHomePath.isEmpty()) {
+ kdeHomePath = QFile::decodeName(qgetenv("KDEHOME"));
+ if (kdeHomePath.isEmpty()) {
+ int kdeSessionVersion = kdeVersion();
+ QDir homeDir(QDir::homePath());
+ QString kdeConfDir(QLatin1String("/.kde"));
+ if (4 == kdeSessionVersion && homeDir.exists(QLatin1String(".kde4")))
+ kdeConfDir = QLatin1String("/.kde4");
+ kdeHomePath = QDir::homePath() + kdeConfDir;
+ }
+ }
+ return kdeHomePath;
+}
+
+void QtIconLoaderImplementation::lookupIconTheme() const
+{
+
+#ifdef Q_WS_X11
+ QString dataDirs = QFile::decodeName(getenv("XDG_DATA_DIRS"));
+ if (dataDirs.isEmpty())
+ dataDirs = QLatin1String("/usr/local/share/:/usr/share/");
+
+ dataDirs.prepend(QDir::homePath() + QLatin1String("/:"));
+ iconDirs = dataDirs.split(QLatin1Char(':'));
+
+ // If we are running GNOME we resolve and use GConf. In all other
+ // cases we currently use the KDE icon theme
+
+ if (qgetenv("DESKTOP_SESSION") == "gnome" ||
+ !qgetenv("GNOME_DESKTOP_SESSION_ID").isEmpty()) {
+
+ if (themeName.isEmpty()) {
+ // Resolve glib and gconf
+
+ p_g_type_init = (Ptr_g_type_init)QLibrary::resolve(QLatin1String("gobject-2.0"), 0, "g_type_init");
+ p_gconf_client_get_default = (Ptr_gconf_client_get_default)QLibrary::resolve(QLatin1String("gconf-2"), 4, "gconf_client_get_default");
+ p_gconf_client_get_string = (Ptr_gconf_client_get_string)QLibrary::resolve(QLatin1String("gconf-2"), 4, "gconf_client_get_string");
+ p_g_object_unref = (Ptr_g_object_unref)QLibrary::resolve(QLatin1String("gobject-2.0"), 0, "g_object_unref");
+ p_g_error_free = (Ptr_g_error_free)QLibrary::resolve(QLatin1String("glib-2.0"), 0, "g_error_free");
+ p_g_free = (Ptr_g_free)QLibrary::resolve(QLatin1String("glib-2.0"), 0, "g_free");
+
+ if (p_g_type_init && p_gconf_client_get_default &&
+ p_gconf_client_get_string && p_g_object_unref &&
+ p_g_error_free && p_g_free) {
+
+ p_g_type_init();
+ GConfClient* client = p_gconf_client_get_default();
+ GError *err = 0;
+
+ char *str = p_gconf_client_get_string(client, "/desktop/gnome/interface/icon_theme", &err);
+ if (!err) {
+ themeName = QString::fromUtf8(str);
+ p_g_free(str);
+ }
+
+ p_g_object_unref(client);
+ if (err)
+ p_g_error_free (err);
+ }
+ if (themeName.isEmpty())
+ themeName = QLatin1String("gnome");
+ }
+
+ if (!themeName.isEmpty())
+ return;
+ }
+
+ // KDE (and others)
+ if (dataDirs.isEmpty())
+ dataDirs = QLatin1String("/usr/local/share/:/usr/share/");
+
+ dataDirs += QLatin1Char(':') + kdeHome() + QLatin1String("/share");
+ dataDirs.prepend(QDir::homePath() + QLatin1String("/:"));
+ QStringList kdeDirs = QFile::decodeName(getenv("KDEDIRS")).split(QLatin1Char(':'));
+ Q_FOREACH (const QString dirName, kdeDirs)
+ dataDirs.append(QLatin1Char(':') + dirName + QLatin1String("/share"));
+ iconDirs = dataDirs.split(QLatin1Char(':'));
+
+ QFileInfo fileInfo(QLatin1String("/usr/share/icons/default.kde"));
+ QDir dir(fileInfo.canonicalFilePath());
+ QString kdeDefault = kdeVersion() >= 4 ? QString::fromLatin1("oxygen") : QString::fromLatin1("crystalsvg");
+ QString defaultTheme = fileInfo.exists() ? dir.dirName() : kdeDefault;
+ QSettings settings(kdeHome() + QLatin1String("/share/config/kdeglobals"), QSettings::IniFormat);
+ settings.beginGroup(QLatin1String("Icons"));
+ themeName = settings.value(QLatin1String("Theme"), defaultTheme).toString();
+#endif
+}
+
+QIconTheme QtIconLoaderImplementation::parseIndexFile(const QString &themeName) const
+{
+ QIconTheme theme;
+ QFile themeIndex;
+ QStringList parents;
+ QHash <int, QString> dirList;
+
+ for ( int i = 0 ; i < iconDirs.size() && !themeIndex.exists() ; ++i) {
+ const QString &contentDir = QLatin1String(iconDirs[i].startsWith(QDir::homePath()) ? "/.icons/" : "/icons/");
+ themeIndex.setFileName(iconDirs[i] + contentDir + themeName + QLatin1String("/index.theme"));
+ }
+
+ if (themeIndex.exists()) {
+ QSettings indexReader(themeIndex.fileName(), QSettings::IniFormat);
+ Q_FOREACH (const QString &key, indexReader.allKeys()) {
+ if (key.endsWith("/Size")) {
+ if (int size = indexReader.value(key).toInt())
+ dirList.insertMulti(size, key.left(key.size() - 5));
+ }
+ }
+
+ // Parent themes provide fallbacks for missing icons
+ parents = indexReader.value(QLatin1String("Icon Theme/Inherits")).toString().split(QLatin1Char(','));
+ }
+
+ if (kdeVersion() >= 3) {
+ QFileInfo fileInfo(QLatin1String("/usr/share/icons/default.kde"));
+ QDir dir(fileInfo.canonicalFilePath());
+ QString defaultKDETheme = dir.exists() ? dir.dirName() : kdeVersion() == 3 ?
+ QString::fromLatin1("crystalsvg") : QString::fromLatin1("oxygen");
+ if (!parents.contains(defaultKDETheme) && themeName != defaultKDETheme)
+ parents.append(defaultKDETheme);
+ } else if (parents.isEmpty() && themeName != QLatin1String("hicolor")) {
+ parents.append(QLatin1String("hicolor"));
+ }
+
+ theme = QIconTheme(dirList, parents);
+ return theme;
+}
+
+QPixmap QtIconLoaderImplementation::findIconHelper(int size, const QString &themeName,
+ const QString &iconName, QStringList &visited) const
+{
+ QPixmap pixmap;
+
+ if (!themeName.isEmpty()) {
+ visited << themeName;
+ QIconTheme theme = themeList.value(themeName);
+
+ if (!theme.isValid()) {
+ theme = parseIndexFile(themeName);
+ themeList.insert(themeName, theme);
+ }
+
+ if (!theme.isValid())
+ return QPixmap();
+
+ QList <QString> subDirs = theme.dirList().values(size);
+
+ for ( int i = 0 ; i < iconDirs.size() ; ++i) {
+ for ( int j = 0 ; j < subDirs.size() ; ++j) {
+ QString contentDir = (iconDirs[i].startsWith(QDir::homePath())) ?
+ QLatin1String("/.icons/") : QLatin1String("/icons/");
+ QString fileName = iconDirs[i] + contentDir + themeName + QLatin1Char('/') + subDirs[j] + QLatin1Char('/') + iconName;
+ QFile file(fileName);
+ if (file.exists())
+ pixmap.load(fileName);
+ if (!pixmap.isNull())
+ break;
+ }
+ }
+
+ if (pixmap.isNull()) {
+ QStringList parents = theme.parents();
+ //search recursively through inherited themes
+ for (int i = 0 ; pixmap.isNull() && i < parents.size() ; ++i) {
+ QString parentTheme = parents[i].trimmed();
+ if (!visited.contains(parentTheme)) //guard against endless recursion
+ pixmap = findIconHelper(size, parentTheme, iconName, visited);
+ }
+ }
+ }
+ return pixmap;
+}
+
+QPixmap QtIconLoaderImplementation::findIcon(int size, const QString &name) const
+{
+ QPixmap pixmap;
+ QString pixmapName = QLatin1String("$qt") + name + QString::number(size);
+ if (QPixmapCache::find(pixmapName, pixmap))
+ return pixmap;
+
+ if (!themeName.isEmpty()) {
+ QStringList visited;
+ pixmap = findIconHelper(size, themeName, name, visited);
+ }
+ QPixmapCache::insert(pixmapName, pixmap);
+ return pixmap;
+}
+#endif //Q_WS_X11
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+
+#ifndef QTICONLOADER_H
+#define QTICONLOADER_H
+
+#include <QtGui/QIcon>
+
+// This is the QtIconLoader
+// Version 0.1
+//
+
+class QtIconLoader
+{
+public:
+ static QIcon icon(const QString &name, const QIcon &fallback = QIcon());
+};
+
+#endif // QTICONLOADER_H
--- /dev/null
+#include <QtGui>
+#include <qtsingleapplication.h>
+#include "Constants.h"
+#include "MainWindow.h"
+
+int main(int argc, char **argv) {
+
+ QtSingleApplication app(argc, argv);
+ if (app.sendMessage("Wake up!"))
+ return 0;
+
+ app.setApplicationName(Constants::APP_NAME);
+ app.setOrganizationName(Constants::ORG_NAME);
+ app.setOrganizationDomain(Constants::ORG_DOMAIN);
+
+ QString locale = ""; //QLocale::system().name();
+
+ // qt translations
+ QTranslator qtTranslator;
+ qtTranslator.load("qt_" + QLocale::system().name(),
+ QLibraryInfo::location(QLibraryInfo::TranslationsPath));
+ app.installTranslator(&qtTranslator);
+
+ // translations
+ QString localeDir = QCoreApplication::applicationDirPath()
+ + QDir::separator() + "locale";
+ QTranslator translator;
+ translator.load(locale, localeDir);
+ app.installTranslator(&translator);
+ QTextCodec::setCodecForTr(QTextCodec::codecForName("utf8"));
+
+ MainWindow mainWin;
+ mainWin.setWindowTitle(Constants::APP_NAME);
+ mainWin.setWindowIcon(QIcon(":/images/app.png"));
+
+ mainWin.show();
+
+ // all string literals are UTF-8
+ QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));
+
+ return app.exec();
+}
--- /dev/null
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+**************************************************************************/
+
+#include "minisplitter.h"
+
+#include <QtGui/QPaintEvent>
+#include <QtGui/QPainter>
+#include <QtGui/QSplitterHandle>
+
+class MiniSplitterHandle : public QSplitterHandle
+{
+public:
+ MiniSplitterHandle(Qt::Orientation orientation, QSplitter *parent)
+ : QSplitterHandle(orientation, parent)
+ {
+ setMask(QRegion(contentsRect()));
+ setAttribute(Qt::WA_MouseNoMask, true);
+ }
+protected:
+ void resizeEvent(QResizeEvent *event);
+ void paintEvent(QPaintEvent *event);
+};
+
+void MiniSplitterHandle::resizeEvent(QResizeEvent *event)
+{
+ if (orientation() == Qt::Horizontal)
+ setContentsMargins(2, 0, 2, 0);
+ else
+ setContentsMargins(0, 2, 0, 2);
+ setMask(QRegion(contentsRect()));
+ QSplitterHandle::resizeEvent(event);
+}
+
+void MiniSplitterHandle::paintEvent(QPaintEvent *event)
+{
+ QPainter painter(this);
+ painter.fillRect(event->rect(), Qt::black);
+}
+
+QSplitterHandle *MiniSplitter::createHandle()
+{
+ return new MiniSplitterHandle(orientation(), this);
+}
+
+MiniSplitter::MiniSplitter(QWidget *parent)
+ : QSplitter(parent)
+{
+ setHandleWidth(1);
+ setChildrenCollapsible(false);
+ setProperty("minisplitter", true);
+}
+
+MiniSplitter::MiniSplitter(Qt::Orientation orientation)
+ : QSplitter(orientation)
+{
+ setHandleWidth(1);
+ setChildrenCollapsible(false);
+ setProperty("minisplitter", true);
+}
--- /dev/null
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+**************************************************************************/
+
+#ifndef MINISPLITTER_H
+#define MINISPLITTER_H
+
+#include <QtGui/QSplitter>
+
+QT_BEGIN_NAMESPACE
+class QSplitterHandle;
+QT_END_NAMESPACE
+
+/*! This is a simple helper-class to obtain mac-style 1-pixel wide splitters */
+class MiniSplitter : public QSplitter
+{
+public:
+ MiniSplitter(QWidget *parent = 0);
+ MiniSplitter(Qt::Orientation orientation);
+
+protected:
+ QSplitterHandle *createHandle();
+};
+
+#endif // MINISPLITTER_H
--- /dev/null
+#include "networkaccess.h"
+#include "Constants.h"
+#include <QtGui>
+
+namespace The {
+ NetworkAccess* http();
+}
+
+NetworkReply::NetworkReply(QNetworkReply *networkReply) : QObject(networkReply) {
+ this->networkReply = networkReply;
+}
+
+void NetworkReply::metaDataChanged() {
+
+ QUrl redirection = networkReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
+ if (redirection.isValid()) {
+
+ qDebug() << "Redirect" << redirection;
+
+ QNetworkReply *redirectReply = The::http()->simpleGet(redirection);
+
+ setParent(redirectReply);
+ networkReply->deleteLater();
+ networkReply = redirectReply;
+
+ // handle redirections
+ connect(networkReply, SIGNAL(metaDataChanged()),
+ this, SLOT(metaDataChanged()), Qt::QueuedConnection);
+
+ // when the request is finished we'll invoke the target method
+ connect(networkReply, SIGNAL(finished()), this, SLOT(finished()), Qt::QueuedConnection);
+
+ }
+}
+
+void NetworkReply::finished() {
+
+ emit finished(networkReply);
+
+ // get the HTTP response body
+ QByteArray bytes = networkReply->readAll();
+
+
+ emit data(bytes);
+
+ // bye bye my reply
+ // this will also delete this NetworkReply as the QNetworkReply is its parent
+ networkReply->deleteLater();
+}
+
+/* --- NetworkAccess --- */
+
+NetworkAccess::NetworkAccess( QObject* parent) : QObject( parent ) {}
+
+QNetworkReply* NetworkAccess::simpleGet(QUrl url) {
+
+ QNetworkAccessManager *manager = The::networkAccessManager();
+
+ QNetworkRequest request(url);
+ request.setRawHeader("User-Agent", Constants::USER_AGENT.toUtf8());
+ request.setRawHeader("Connection", "Keep-Alive");
+ qDebug() << "GET" << url.toString();
+ QNetworkReply *networkReply = manager->get(request);
+
+ // error handling
+ connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)),
+ this, SLOT(error(QNetworkReply::NetworkError)));
+
+ return networkReply;
+
+}
+
+NetworkReply* NetworkAccess::get(const QUrl url) {
+
+ QNetworkReply *networkReply = simpleGet(url);
+ NetworkReply *reply = new NetworkReply(networkReply);
+
+ // handle redirections
+ connect(networkReply, SIGNAL(metaDataChanged()),
+ reply, SLOT(metaDataChanged()), Qt::QueuedConnection);
+
+ // when the request is finished we'll invoke the target method
+ connect(networkReply, SIGNAL(finished()), reply, SLOT(finished()), Qt::QueuedConnection);
+
+ return reply;
+
+}
+
+QNetworkReply* NetworkAccess::syncGet(QUrl url) {
+
+ working = true;
+
+ networkReply = simpleGet(url);
+ connect(networkReply, SIGNAL(metaDataChanged()),
+ this, SLOT(syncMetaDataChanged()), Qt::QueuedConnection);
+ connect(networkReply, SIGNAL(finished()),
+ this, SLOT(syncFinished()), Qt::QueuedConnection);
+ connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)),
+ this, SLOT(error(QNetworkReply::NetworkError)));
+
+ // A little trick to make this function blocking
+ while (working) {
+ // Do something else, maybe even network processing events
+ qApp->processEvents();
+ }
+
+ networkReply->deleteLater();
+ return networkReply;
+
+}
+
+void NetworkAccess::syncMetaDataChanged() {
+
+ QUrl redirection = networkReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
+ if (redirection.isValid()) {
+
+ qDebug() << "Redirect" << redirection;
+ networkReply->deleteLater();
+ syncGet(redirection);
+
+ /*
+ QNetworkAccessManager *manager = The::networkAccessManager();
+ networkReply->deleteLater();
+ networkReply = manager->get(QNetworkRequest(redirection));
+ connect(networkReply, SIGNAL(metaDataChanged()),
+ this, SLOT(metaDataChanged()), Qt::QueuedConnection);
+ connect(networkReply, SIGNAL(finished()),
+ this, SLOT(finished()), Qt::QueuedConnection);
+ */
+ }
+
+}
+
+void NetworkAccess::syncFinished() {
+ // got it!
+ working = false;
+}
+
+void NetworkAccess::error(QNetworkReply::NetworkError code) {
+ // get the QNetworkReply that sent the signal
+ QNetworkReply *networkReply = static_cast<QNetworkReply *>(sender());
+ if (!networkReply) {
+ qDebug() << "Cannot get sender";
+ return;
+ }
+
+ // report the error in the status bar
+ QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(qApp->topLevelWidgets().first());
+ if (mainWindow) mainWindow->statusBar()->showMessage(networkReply->errorString());
+
+ qDebug() << "Network error" << networkReply->errorString() << code;
+ networkReply->deleteLater();
+}
+
+QByteArray NetworkAccess::syncGetBytes(QUrl url) {
+ return syncGet(url)->readAll();
+}
+
+QString NetworkAccess::syncGetString(QUrl url) {
+ return QString::fromUtf8(syncGetBytes(url));
+}
--- /dev/null
+#ifndef NETWORKACCESS_H
+#define NETWORKACCESS_H
+
+#include <QtNetwork>
+
+namespace The {
+ QNetworkAccessManager* networkAccessManager();
+}
+
+class NetworkReply : public QObject {
+
+ Q_OBJECT
+
+public:
+ NetworkReply(QNetworkReply* networkReply);
+
+public slots:
+ void finished();
+ void metaDataChanged();
+
+signals:
+ void data(QByteArray);
+ void finished(QNetworkReply*);
+
+private:
+ QNetworkReply *networkReply;
+
+};
+
+
+class NetworkAccess : public QObject {
+
+ Q_OBJECT
+
+public:
+ NetworkAccess( QObject* parent=0);
+ QNetworkReply* simpleGet(QUrl url);
+ NetworkReply* get(QUrl url);
+ QNetworkReply* syncGet(QUrl url);
+ QByteArray syncGetBytes(QUrl url);
+ QString syncGetString(QUrl url);
+
+private slots:
+ void error(QNetworkReply::NetworkError);
+ void syncMetaDataChanged();
+ void syncFinished();
+
+private:
+ QNetworkReply *networkReply;
+ bool working;
+
+};
+
+typedef QPointer<QObject> ObjectPointer;
+Q_DECLARE_METATYPE(ObjectPointer)
+
+#endif // NETWORKACCESS_H
--- /dev/null
+#include "PrettyItemDelegate.h"
+#include "../ListModel.h"
+
+#include <QFontMetricsF>
+#include <QPainter>
+
+using namespace Playlist;
+
+const qreal PrettyItemDelegate::THUMB_HEIGHT = 90.0;
+const qreal PrettyItemDelegate::THUMB_WIDTH = 120.0;
+const qreal PrettyItemDelegate::MARGIN = 0.0;
+const qreal PrettyItemDelegate::MARGINH = 0.0;
+const qreal PrettyItemDelegate::MARGINBODY = 0.0;
+const qreal PrettyItemDelegate::PADDING = 10.0;
+
+PrettyItemDelegate::PrettyItemDelegate( QObject* parent ) : QStyledItemDelegate( parent ) {
+
+}
+
+PrettyItemDelegate::~PrettyItemDelegate() { }
+
+QSize PrettyItemDelegate::sizeHint( const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const {
+ return QSize( 256, THUMB_HEIGHT+1.0);
+}
+
+void PrettyItemDelegate::paint( QPainter* painter,
+ const QStyleOptionViewItem& option, const QModelIndex& index ) const {
+
+ int itemType = index.data(ItemTypeRole).toInt();
+ if (itemType == ItemTypeVideo) {
+ QApplication::style()->drawPrimitive( QStyle::PE_PanelItemViewItem, &option, painter );
+ paintBody( painter, option, index );
+ } else
+ QStyledItemDelegate::paint( painter, option, index );
+
+}
+
+void PrettyItemDelegate::paintBody( QPainter* painter,
+ const QStyleOptionViewItem& option,
+ const QModelIndex& index ) const {
+
+ painter->save();
+ painter->translate( option.rect.topLeft() );
+
+ const QRectF line(0, 0, option.rect.width(), option.rect.height());
+
+ QPalette palette;
+
+ QFont boldFont;
+ boldFont.setBold(true);
+ QFont smallerFont;
+ smallerFont.setPointSize(smallerFont.pointSize()*.85);
+ QFont smallerBoldFont;
+ smallerBoldFont.setBold(true);
+ smallerBoldFont.setPointSize(smallerBoldFont.pointSize()*.85);
+
+ const bool isActive = index.data( ActiveTrackRole ).toBool();
+ const bool isSelected = option.state & QStyle::State_Selected;
+ if (isActive) {
+ // draw the "current track" highlight underneath the text
+ if (!isSelected)
+ paintActiveOverlay(painter, line.x(), line.y(), line.width(), line.height());
+ }
+
+ // get the video metadata
+ const VideoPointer videoPointer = index.data( VideoRole ).value<VideoPointer>();
+ const Video *video = videoPointer.data();
+
+ // thumb
+ painter->drawImage(QRect(0, 0, THUMB_WIDTH, THUMB_HEIGHT), video->thumbnail());
+
+ // time
+ QString timeString;
+ int duration = video->duration();
+ if ( duration > 3600 )
+ timeString = QTime().addSecs(duration).toString("h:mm:ss");
+ else
+ timeString = QTime().addSecs(duration).toString("m:ss");
+ drawTime(painter, timeString, line);
+
+ if (isActive) painter->setFont(boldFont);
+ const QFontMetricsF fm(painter->font());
+ const QFontMetricsF boldMetrics(boldFont);
+
+ // text color
+ if (isSelected)
+ painter->setPen(QPen(option.palette.brush(QPalette::HighlightedText), 0));
+ else
+ painter->setPen(QPen(option.palette.brush(QPalette::Text), 0));
+
+ // title
+ QString videoTitle = video->title();
+ QRectF textBox = line.adjusted(PADDING+THUMB_WIDTH, PADDING, -2 * PADDING, -PADDING);
+ textBox = painter->boundingRect( textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, videoTitle);
+ painter->drawText(textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, videoTitle);
+
+ painter->setFont(smallerFont);
+
+ // published date
+ QString publishedString = video->published().date().toString(Qt::DefaultLocaleShortDate);
+ QSizeF publishedStringSize(QFontMetrics(painter->font()).size( Qt::TextSingleLine, publishedString ) );
+ QPointF textLoc(PADDING+THUMB_WIDTH, PADDING*2 + textBox.height());
+ QRectF publishedTextBox( textLoc , publishedStringSize);
+ painter->drawText(publishedTextBox, Qt::AlignLeft | Qt::AlignTop, publishedString);
+
+ // author
+ painter->save();
+ painter->setFont(smallerBoldFont);
+ if (!isSelected)
+ painter->setPen(QPen(option.palette.brush(QPalette::Mid), 0));
+ QString authorString = video->author();
+ QSizeF authorStringSize(QFontMetrics(painter->font()).size( Qt::TextSingleLine, authorString ) );
+ textLoc.setX(textLoc.x() + publishedStringSize.width() + PADDING);
+ QRectF authorTextBox( textLoc , authorStringSize);
+ painter->drawText(authorTextBox, Qt::AlignLeft | Qt::AlignTop, authorString);
+ painter->restore();
+
+ // view count
+ if (video->viewCount() >= 0) {
+ painter->save();
+ QLocale locale;
+ QString viewCountString = tr("%1 views").arg(locale.toString(video->viewCount()));
+ QSizeF viewCountStringSize(QFontMetrics(painter->font()).size( Qt::TextSingleLine, viewCountString ) );
+ textLoc.setX(textLoc.x() + authorStringSize.width() + PADDING);
+ QRectF viewCountTextBox( textLoc , viewCountStringSize);
+ painter->drawText(viewCountTextBox, Qt::AlignLeft | Qt::AlignBottom, viewCountString);
+ painter->restore();
+ }
+
+ /*
+ QLinearGradient myGradient;
+ QPen myPen;
+ QFont myFont;
+ QPointF baseline(authorTextBox.x(), authorTextBox.y() + authorTextBox.height());
+ QPainterPath myPath;
+ myPath.addText(baseline, boldFont, authorString);
+ painter->setBrush(palette.color(QPalette::WindowText));
+ painter->setPen(palette.color(QPalette::Dark));
+ painter->setRenderHints (QPainter::Antialiasing, true);
+ painter->drawPath(myPath);
+ */
+
+ // separator
+ painter->setPen(palette.color(QPalette::Midlight));
+ painter->drawLine(0, THUMB_HEIGHT, line.width(), THUMB_HEIGHT);
+
+ painter->restore();
+
+}
+
+QPointF PrettyItemDelegate::centerImage( const QPixmap& pixmap, const QRectF& rect ) const {
+ qreal pixmapRatio = ( qreal )pixmap.width() / ( qreal )pixmap.height();
+
+ qreal moveByX = 0.0;
+ qreal moveByY = 0.0;
+
+ if ( pixmapRatio >= 1 )
+ moveByY = ( rect.height() - ( rect.width() / pixmapRatio ) ) / 2.0;
+ else
+ moveByX = ( rect.width() - ( rect.height() * pixmapRatio ) ) / 2.0;
+
+ return QPointF( moveByX, moveByY );
+}
+
+void PrettyItemDelegate::paintActiveOverlay( QPainter *painter, qreal x, qreal y, qreal w, qreal h ) const {
+
+ QPalette palette;
+ QColor color2 = palette.color( QPalette::Highlight);
+ QColor backgroundColor = palette.color( QPalette::Base);
+ float animation = 0.5;
+ color2 = QColor::fromHsv(
+ animation == 0.0 ? backgroundColor.hue() : color2.hue(),
+ (int)(backgroundColor.saturation()*(1.0f-animation)+color2.saturation()*animation),
+ (int)(backgroundColor.value()*(1.0f-animation)+color2.value()*animation)
+ );
+ QColor color1 = QColor::fromHsv(
+ color2.hue(),
+ (color2.saturation() - 16 > 0) ? color2.saturation() - 16 : 0,
+ (color2.value() + 16 < 255) ? color2.value() + 16 : 255
+ );
+ QRect rect((int) x, (int) y, (int) w, (int) h);
+ painter->save();
+ painter->setPen(Qt::NoPen);
+ QLinearGradient linearGradient(0, 0, 0, rect.height());
+ linearGradient.setColorAt(0.0, color1);
+ linearGradient.setColorAt(1.0, color2);
+ painter->setBrush(linearGradient);
+ painter->drawRect(rect);
+ painter->restore();
+}
+
+void PrettyItemDelegate::drawTime(QPainter *painter, QString time, QRectF line) const {
+ static const int timePadding = 4;
+ QRectF textBox = painter->boundingRect(line, Qt::AlignLeft | Qt::AlignTop, time);
+ // add padding
+ textBox.adjust(0, 0, timePadding, 0);
+ // move to bottom right corner of the thumb
+ textBox.translate(THUMB_WIDTH - textBox.width(), THUMB_HEIGHT - textBox.height());
+
+ painter->save();
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(Qt::black);
+ painter->setOpacity(.5);
+ painter->drawRect(textBox);
+ painter->restore();
+
+ painter->save();
+ painter->setPen(Qt::white);
+ painter->drawText(textBox, Qt::AlignCenter, time);
+ painter->restore();
+}
--- /dev/null
+#ifndef PRETTYITEMDELEGATE_H
+#define PRETTYITEMDELEGATE_H
+
+#include <QModelIndex>
+#include <QStyledItemDelegate>
+
+class QPainter;
+
+
+namespace Playlist {
+
+ class PrettyItemDelegate : public QStyledItemDelegate {
+
+ Q_OBJECT
+
+ public:
+ PrettyItemDelegate( QObject* parent = 0 );
+ ~PrettyItemDelegate();
+
+ QSize sizeHint( const QStyleOptionViewItem&, const QModelIndex& ) const;
+ void paint( QPainter*, const QStyleOptionViewItem&, const QModelIndex& ) const;
+
+ private:
+ void paintBody( QPainter*, const QStyleOptionViewItem&, const QModelIndex& ) const;
+
+ QPointF centerImage( const QPixmap&, const QRectF& ) const;
+
+ /**
+ * Paints a marker indicating the track is active
+ */
+ void paintActiveOverlay( QPainter *painter, qreal x, qreal y, qreal w, qreal h ) const;
+ /**
+ * Paints the video duration
+ */
+ void drawTime(QPainter *painter, QString time, QRectF line) const;
+
+ static const qreal THUMB_WIDTH;
+ static const qreal THUMB_HEIGHT;
+ static const qreal MARGIN;
+ static const qreal MARGINH;
+ static const qreal MARGINBODY;
+ static const qreal PADDING;
+
+ };
+}
+
+#endif
+
--- /dev/null
+#include "playlistwidget.h"
+
+PlaylistWidget::PlaylistWidget (QWidget *parent, THBlackBar *tabBar, QListView *listView)
+ : QWidget(parent) {
+ QBoxLayout *layout = new QVBoxLayout();
+ layout->setMargin(0);
+ layout->setSpacing(0);
+ layout->addWidget(tabBar);
+ layout->addWidget(listView);
+ setLayout(layout);
+}
--- /dev/null
+#ifndef PLAYLISTWIDGET_H
+#define PLAYLISTWIDGET_H
+
+#include <QtGui>
+#include "thblackbar.h"
+
+class PlaylistWidget : public QWidget
+{
+public:
+ PlaylistWidget(QWidget *parent, THBlackBar *tabBar, QListView *listView);
+};
+
+#endif // PLAYLISTWIDGET_H
--- /dev/null
+#include "qtlockedfile.h"
--- /dev/null
+#include "qtsingleapplication.h"
--- /dev/null
+/****************************************************************************
+**
+** This file is part of a Qt Solutions component.
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Solutions Commercial License Agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** Please note Third Party Software included with Qt Solutions may impose
+** additional restrictions and it is the user's responsibility to ensure
+** that they have met the licensing requirements of the GPL, LGPL, or Qt
+** Solutions Commercial license and the relevant license of the Third
+** Party Software they are using.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qtlocalpeer.h"
+#include <QtCore/QCoreApplication>
+#include <QtCore/QTime>
+
+#if defined(Q_OS_WIN)
+#include <QtCore/QLibrary>
+#include <QtCore/qt_windows.h>
+typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*);
+static PProcessIdToSessionId pProcessIdToSessionId = 0;
+#endif
+#if defined(Q_OS_UNIX)
+#include <time.h>
+#endif
+
+namespace QtLP_Private {
+#include "qtlockedfile.cpp"
+#if defined(Q_OS_WIN)
+#include "qtlockedfile_win.cpp"
+#else
+#include "qtlockedfile_unix.cpp"
+#endif
+}
+
+const char* QtLocalPeer::ack = "ack";
+
+QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
+ : QObject(parent), id(appId)
+{
+ QString prefix = id;
+ if (id.isEmpty()) {
+ id = QCoreApplication::applicationFilePath();
+#if defined(Q_OS_WIN)
+ id = id.toLower();
+#endif
+ prefix = id.section(QLatin1Char('/'), -1);
+ }
+ prefix.remove(QRegExp("[^a-zA-Z]"));
+ prefix.truncate(6);
+
+ QByteArray idc = id.toUtf8();
+ quint16 idNum = qChecksum(idc.constData(), idc.size());
+ socketName = QLatin1String("qtsingleapp-") + prefix
+ + QLatin1Char('-') + QString::number(idNum, 16);
+
+#if defined(Q_OS_WIN)
+ if (!pProcessIdToSessionId) {
+ QLibrary lib("kernel32");
+ pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId");
+ }
+ if (pProcessIdToSessionId) {
+ DWORD sessionId = 0;
+ pProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
+ socketName += QLatin1Char('-') + QString::number(sessionId, 16);
+ }
+#else
+ socketName += QLatin1Char('-') + QString::number(::getuid(), 16);
+#endif
+
+ server = new QLocalServer(this);
+ QString lockName = QDir(QDir::tempPath()).absolutePath()
+ + QLatin1Char('/') + socketName
+ + QLatin1String("-lockfile");
+ lockFile.setFileName(lockName);
+ lockFile.open(QIODevice::ReadWrite);
+}
+
+
+
+bool QtLocalPeer::isClient()
+{
+ if (lockFile.isLocked())
+ return false;
+
+ if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false))
+ return true;
+
+ bool res = server->listen(socketName);
+#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0))
+ // ### Workaround
+ if (!res && server->serverError() == QAbstractSocket::AddressInUseError) {
+ QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName);
+ res = server->listen(socketName);
+ }
+#endif
+ if (!res)
+ qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString()));
+ QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection()));
+ return false;
+}
+
+
+bool QtLocalPeer::sendMessage(const QString &message, int timeout)
+{
+ if (!isClient())
+ return false;
+
+ QLocalSocket socket;
+ bool connOk = false;
+ for(int i = 0; i < 2; i++) {
+ // Try twice, in case the other instance is just starting up
+ socket.connectToServer(socketName);
+ connOk = socket.waitForConnected(timeout/2);
+ if (connOk || i)
+ break;
+ int ms = 250;
+#if defined(Q_OS_WIN)
+ Sleep(DWORD(ms));
+#else
+ struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
+ nanosleep(&ts, NULL);
+#endif
+ }
+ if (!connOk)
+ return false;
+
+ QByteArray uMsg(message.toUtf8());
+ QDataStream ds(&socket);
+ ds.writeBytes(uMsg.constData(), uMsg.size());
+ bool res = socket.waitForBytesWritten(timeout);
+ res &= socket.waitForReadyRead(timeout); // wait for ack
+ res &= (socket.read(qstrlen(ack)) == ack);
+ return res;
+}
+
+
+void QtLocalPeer::receiveConnection()
+{
+ QLocalSocket* socket = server->nextPendingConnection();
+ if (!socket)
+ return;
+
+ while (socket->bytesAvailable() < (int)sizeof(quint32))
+ socket->waitForReadyRead();
+ QDataStream ds(socket);
+ QByteArray uMsg;
+ quint32 remaining;
+ ds >> remaining;
+ uMsg.resize(remaining);
+ int got = 0;
+ char* uMsgBuf = uMsg.data();
+ do {
+ got = ds.readRawData(uMsgBuf, remaining);
+ remaining -= got;
+ uMsgBuf += got;
+ } while (remaining && got >= 0 && socket->waitForReadyRead(2000));
+ if (got < 0) {
+ qWarning() << "QtLocalPeer: Message reception failed" << socket->errorString();
+ delete socket;
+ return;
+ }
+ QString message(QString::fromUtf8(uMsg));
+ socket->write(ack, qstrlen(ack));
+ socket->waitForBytesWritten(1000);
+ delete socket;
+ emit messageReceived(message); //### (might take a long time to return)
+}
--- /dev/null
+/****************************************************************************
+**
+** This file is part of a Qt Solutions component.
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Solutions Commercial License Agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** Please note Third Party Software included with Qt Solutions may impose
+** additional restrictions and it is the user's responsibility to ensure
+** that they have met the licensing requirements of the GPL, LGPL, or Qt
+** Solutions Commercial license and the relevant license of the Third
+** Party Software they are using.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+
+#include <QtNetwork/QLocalServer>
+#include <QtNetwork/QLocalSocket>
+#include <QtCore/QDir>
+
+namespace QtLP_Private {
+#include "qtlockedfile.h"
+}
+
+class QtLocalPeer : public QObject
+{
+ Q_OBJECT
+
+public:
+ QtLocalPeer(QObject *parent = 0, const QString &appId = QString());
+ bool isClient();
+ bool sendMessage(const QString &message, int timeout);
+ QString applicationId() const
+ { return id; }
+
+Q_SIGNALS:
+ void messageReceived(const QString &message);
+
+protected Q_SLOTS:
+ void receiveConnection();
+
+protected:
+ QString id;
+ QString socketName;
+ QLocalServer* server;
+ QtLP_Private::QtLockedFile lockFile;
+
+private:
+ static const char* ack;
+};
--- /dev/null
+/****************************************************************************
+**
+** This file is part of a Qt Solutions component.
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Solutions Commercial License Agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** Please note Third Party Software included with Qt Solutions may impose
+** additional restrictions and it is the user's responsibility to ensure
+** that they have met the licensing requirements of the GPL, LGPL, or Qt
+** Solutions Commercial license and the relevant license of the Third
+** Party Software they are using.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+#include "qtlockedfile.h"
+
+/*!
+ \class QtLockedFile
+
+ \brief The QtLockedFile class extends QFile with advisory locking
+ functions.
+
+ A file may be locked in read or write mode. Multiple instances of
+ \e QtLockedFile, created in multiple processes running on the same
+ machine, may have a file locked in read mode. Exactly one instance
+ may have it locked in write mode. A read and a write lock cannot
+ exist simultaneously on the same file.
+
+ The file locks are advisory. This means that nothing prevents
+ another process from manipulating a locked file using QFile or
+ file system functions offered by the OS. Serialization is only
+ guaranteed if all processes that access the file use
+ QLockedFile. Also, while holding a lock on a file, a process
+ must not open the same file again (through any API), or locks
+ can be unexpectedly lost.
+
+ The lock provided by an instance of \e QtLockedFile is released
+ whenever the program terminates. This is true even when the
+ program crashes and no destructors are called.
+*/
+
+/*! \enum QtLockedFile::LockMode
+
+ This enum describes the available lock modes.
+
+ \value ReadLock A read lock.
+ \value WriteLock A write lock.
+ \value NoLock Neither a read lock nor a write lock.
+*/
+
+/*!
+ Constructs an unlocked \e QtLockedFile object. This constructor
+ behaves in the same way as \e QFile::QFile().
+
+ \sa QFile::QFile()
+*/
+QtLockedFile::QtLockedFile()
+ : QFile()
+{
+#ifdef Q_OS_WIN
+ wmutex = 0;
+ rmutex = 0;
+#endif
+ m_lock_mode = NoLock;
+}
+
+/*!
+ Constructs an unlocked QtLockedFile object with file \a name. This
+ constructor behaves in the same way as \e QFile::QFile(const
+ QString&).
+
+ \sa QFile::QFile()
+*/
+QtLockedFile::QtLockedFile(const QString &name)
+ : QFile(name)
+{
+#ifdef Q_OS_WIN
+ wmutex = 0;
+ rmutex = 0;
+#endif
+ m_lock_mode = NoLock;
+}
+
+/*!
+ Opens the file in OpenMode \a mode.
+
+ This is identical to QFile::open(), with the one exception that the
+ Truncate mode flag is disallowed. Truncation would conflict with the
+ advisory file locking, since the file would be modified before the
+ write lock is obtained. If truncation is required, use resize(0)
+ after obtaining the write lock.
+
+ Returns true if successful; otherwise false.
+
+ \sa QFile::open(), QFile::resize()
+*/
+bool QtLockedFile::open(OpenMode mode)
+{
+ if (mode & QIODevice::Truncate) {
+ qWarning("QtLockedFile::open(): Truncate mode not allowed.");
+ return false;
+ }
+ return QFile::open(mode);
+}
+
+/*!
+ Returns \e true if this object has a in read or write lock;
+ otherwise returns \e false.
+
+ \sa lockMode()
+*/
+bool QtLockedFile::isLocked() const
+{
+ return m_lock_mode != NoLock;
+}
+
+/*!
+ Returns the type of lock currently held by this object, or \e
+ QtLockedFile::NoLock.
+
+ \sa isLocked()
+*/
+QtLockedFile::LockMode QtLockedFile::lockMode() const
+{
+ return m_lock_mode;
+}
+
+/*!
+ \fn bool QtLockedFile::lock(LockMode mode, bool block = true)
+
+ Obtains a lock of type \a mode. The file must be opened before it
+ can be locked.
+
+ If \a block is true, this function will block until the lock is
+ aquired. If \a block is false, this function returns \e false
+ immediately if the lock cannot be aquired.
+
+ If this object already has a lock of type \a mode, this function
+ returns \e true immediately. If this object has a lock of a
+ different type than \a mode, the lock is first released and then a
+ new lock is obtained.
+
+ This function returns \e true if, after it executes, the file is
+ locked by this object, and \e false otherwise.
+
+ \sa unlock(), isLocked(), lockMode()
+*/
+
+/*!
+ \fn bool QtLockedFile::unlock()
+
+ Releases a lock.
+
+ If the object has no lock, this function returns immediately.
+
+ This function returns \e true if, after it executes, the file is
+ not locked by this object, and \e false otherwise.
+
+ \sa lock(), isLocked(), lockMode()
+*/
+
+/*!
+ \fn QtLockedFile::~QtLockedFile()
+
+ Destroys the \e QtLockedFile object. If any locks were held, they
+ are released.
+*/
--- /dev/null
+/****************************************************************************
+**
+** This file is part of a Qt Solutions component.
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Solutions Commercial License Agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** Please note Third Party Software included with Qt Solutions may impose
+** additional restrictions and it is the user's responsibility to ensure
+** that they have met the licensing requirements of the GPL, LGPL, or Qt
+** Solutions Commercial license and the relevant license of the Third
+** Party Software they are using.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+#ifndef QTLOCKEDFILE_H
+#define QTLOCKEDFILE_H
+
+#include <QtCore/QFile>
+#ifdef Q_OS_WIN
+#include <QtCore/QVector>
+#endif
+
+#if defined(Q_WS_WIN)
+# if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT)
+# define QT_QTLOCKEDFILE_EXPORT
+# elif defined(QT_QTLOCKEDFILE_IMPORT)
+# if defined(QT_QTLOCKEDFILE_EXPORT)
+# undef QT_QTLOCKEDFILE_EXPORT
+# endif
+# define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport)
+# elif defined(QT_QTLOCKEDFILE_EXPORT)
+# undef QT_QTLOCKEDFILE_EXPORT
+# define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport)
+# endif
+#else
+# define QT_QTLOCKEDFILE_EXPORT
+#endif
+
+class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile
+{
+public:
+ enum LockMode { NoLock = 0, ReadLock, WriteLock };
+
+ QtLockedFile();
+ QtLockedFile(const QString &name);
+ ~QtLockedFile();
+
+ bool open(OpenMode mode);
+
+ bool lock(LockMode mode, bool block = true);
+ bool unlock();
+ bool isLocked() const;
+ LockMode lockMode() const;
+
+private:
+#ifdef Q_OS_WIN
+ Qt::HANDLE wmutex;
+ Qt::HANDLE rmutex;
+ QVector<Qt::HANDLE> rmutexes;
+ QString mutexname;
+
+ Qt::HANDLE getMutexHandle(int idx, bool doCreate);
+ bool waitMutex(Qt::HANDLE mutex, bool doBlock);
+
+#endif
+ LockMode m_lock_mode;
+};
+
+#endif
--- /dev/null
+/****************************************************************************
+**
+** This file is part of a Qt Solutions component.
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Solutions Commercial License Agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** Please note Third Party Software included with Qt Solutions may impose
+** additional restrictions and it is the user's responsibility to ensure
+** that they have met the licensing requirements of the GPL, LGPL, or Qt
+** Solutions Commercial license and the relevant license of the Third
+** Party Software they are using.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "qtlockedfile.h"
+
+bool QtLockedFile::lock(LockMode mode, bool block)
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::lock(): file is not opened");
+ return false;
+ }
+
+ if (mode == NoLock)
+ return unlock();
+
+ if (mode == m_lock_mode)
+ return true;
+
+ if (m_lock_mode != NoLock)
+ unlock();
+
+ struct flock fl;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK;
+ int cmd = block ? F_SETLKW : F_SETLK;
+ int ret = fcntl(handle(), cmd, &fl);
+
+ if (ret == -1) {
+ if (errno != EINTR && errno != EAGAIN)
+ qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
+ return false;
+ }
+
+
+ m_lock_mode = mode;
+ return true;
+}
+
+
+bool QtLockedFile::unlock()
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::unlock(): file is not opened");
+ return false;
+ }
+
+ if (!isLocked())
+ return true;
+
+ struct flock fl;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_type = F_UNLCK;
+ int ret = fcntl(handle(), F_SETLKW, &fl);
+
+ if (ret == -1) {
+ qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno));
+ return false;
+ }
+
+ m_lock_mode = NoLock;
+ return true;
+}
+
+QtLockedFile::~QtLockedFile()
+{
+ if (isOpen())
+ unlock();
+}
+
--- /dev/null
+/****************************************************************************
+**
+** This file is part of a Qt Solutions component.
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Solutions Commercial License Agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** Please note Third Party Software included with Qt Solutions may impose
+** additional restrictions and it is the user's responsibility to ensure
+** that they have met the licensing requirements of the GPL, LGPL, or Qt
+** Solutions Commercial license and the relevant license of the Third
+** Party Software they are using.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+#include "qtlockedfile.h"
+#include <qt_windows.h>
+#include <QtCore/QFileInfo>
+
+#define MUTEX_PREFIX "QtLockedFile mutex "
+// Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS
+#define MAX_READERS MAXIMUM_WAIT_OBJECTS
+
+Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate)
+{
+ if (mutexname.isEmpty()) {
+ QFileInfo fi(*this);
+ mutexname = QString::fromLatin1(MUTEX_PREFIX)
+ + fi.absoluteFilePath().toLower();
+ }
+ QString mname(mutexname);
+ if (idx >= 0)
+ mname += QString::number(idx);
+
+ Qt::HANDLE mutex;
+ if (doCreate) {
+ QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); },
+ { mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } );
+ if (!mutex) {
+ qErrnoWarning("QtLockedFile::lock(): CreateMutex failed");
+ return 0;
+ }
+ }
+ else {
+ QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); },
+ { mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } );
+ if (!mutex) {
+ if (GetLastError() != ERROR_FILE_NOT_FOUND)
+ qErrnoWarning("QtLockedFile::lock(): OpenMutex failed");
+ return 0;
+ }
+ }
+ return mutex;
+}
+
+bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock)
+{
+ Q_ASSERT(mutex);
+ DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0);
+ switch (res) {
+ case WAIT_OBJECT_0:
+ case WAIT_ABANDONED:
+ return true;
+ break;
+ case WAIT_TIMEOUT:
+ break;
+ default:
+ qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed");
+ }
+ return false;
+}
+
+
+
+bool QtLockedFile::lock(LockMode mode, bool block)
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::lock(): file is not opened");
+ return false;
+ }
+
+ if (mode == NoLock)
+ return unlock();
+
+ if (mode == m_lock_mode)
+ return true;
+
+ if (m_lock_mode != NoLock)
+ unlock();
+
+ if (!wmutex && !(wmutex = getMutexHandle(-1, true)))
+ return false;
+
+ if (!waitMutex(wmutex, block))
+ return false;
+
+ if (mode == ReadLock) {
+ int idx = 0;
+ for (; idx < MAX_READERS; idx++) {
+ rmutex = getMutexHandle(idx, false);
+ if (!rmutex || waitMutex(rmutex, false))
+ break;
+ CloseHandle(rmutex);
+ }
+ bool ok = true;
+ if (idx >= MAX_READERS) {
+ qWarning("QtLockedFile::lock(): too many readers");
+ rmutex = 0;
+ ok = false;
+ }
+ else if (!rmutex) {
+ rmutex = getMutexHandle(idx, true);
+ if (!rmutex || !waitMutex(rmutex, false))
+ ok = false;
+ }
+ if (!ok && rmutex) {
+ CloseHandle(rmutex);
+ rmutex = 0;
+ }
+ ReleaseMutex(wmutex);
+ if (!ok)
+ return false;
+ }
+ else {
+ Q_ASSERT(rmutexes.isEmpty());
+ for (int i = 0; i < MAX_READERS; i++) {
+ Qt::HANDLE mutex = getMutexHandle(i, false);
+ if (mutex)
+ rmutexes.append(mutex);
+ }
+ if (rmutexes.size()) {
+ DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(),
+ TRUE, block ? INFINITE : 0);
+ if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) {
+ if (res != WAIT_TIMEOUT)
+ qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed");
+ m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky
+ unlock();
+ return false;
+ }
+ }
+ }
+
+ m_lock_mode = mode;
+ return true;
+}
+
+bool QtLockedFile::unlock()
+{
+ if (!isOpen()) {
+ qWarning("QtLockedFile::unlock(): file is not opened");
+ return false;
+ }
+
+ if (!isLocked())
+ return true;
+
+ if (m_lock_mode == ReadLock) {
+ ReleaseMutex(rmutex);
+ CloseHandle(rmutex);
+ rmutex = 0;
+ }
+ else {
+ foreach(Qt::HANDLE mutex, rmutexes) {
+ ReleaseMutex(mutex);
+ CloseHandle(mutex);
+ }
+ rmutexes.clear();
+ ReleaseMutex(wmutex);
+ }
+
+ m_lock_mode = QtLockedFile::NoLock;
+ return true;
+}
+
+QtLockedFile::~QtLockedFile()
+{
+ if (isOpen())
+ unlock();
+ if (wmutex)
+ CloseHandle(wmutex);
+}
--- /dev/null
+/****************************************************************************
+**
+** This file is part of a Qt Solutions component.
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Solutions Commercial License Agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** Please note Third Party Software included with Qt Solutions may impose
+** additional restrictions and it is the user's responsibility to ensure
+** that they have met the licensing requirements of the GPL, LGPL, or Qt
+** Solutions Commercial license and the relevant license of the Third
+** Party Software they are using.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qtsingleapplication.h"
+#include "qtlocalpeer.h"
+#include <QtGui/QWidget>
+
+
+/*!
+ \class QtSingleApplication qtsingleapplication.h
+ \brief The QtSingleApplication class provides an API to detect and
+ communicate with running instances of an application.
+
+ This class allows you to create applications where only one
+ instance should be running at a time. I.e., if the user tries to
+ launch another instance, the already running instance will be
+ activated instead. Another usecase is a client-server system,
+ where the first started instance will assume the role of server,
+ and the later instances will act as clients of that server.
+
+ By default, the full path of the executable file is used to
+ determine whether two processes are instances of the same
+ application. You can also provide an explicit identifier string
+ that will be compared instead.
+
+ The application should create the QtSingleApplication object early
+ in the startup phase, and call isRunning() or sendMessage() to
+ find out if another instance of this application is already
+ running. Startup parameters (e.g. the name of the file the user
+ wanted this new instance to open) can be passed to the running
+ instance in the sendMessage() function.
+
+ If isRunning() or sendMessage() returns false, it means that no
+ other instance is running, and this instance has assumed the role
+ as the running instance. The application should continue with the
+ initialization of the application user interface before entering
+ the event loop with exec(), as normal. The messageReceived()
+ signal will be emitted when the application receives messages from
+ another instance of the same application.
+
+ If isRunning() or sendMessage() returns true, another instance is
+ already running, and the application should terminate or enter
+ client mode.
+
+ If a message is received it might be helpful to the user to raise
+ the application so that it becomes visible. To facilitate this,
+ QtSingleApplication provides the setActivationWindow() function
+ and the activateWindow() slot.
+
+ Here's an example that shows how to convert an existing
+ application to use QtSingleApplication. It is very simple and does
+ not make use of all QtSingleApplication's functionality (see the
+ examples for that).
+
+ \code
+ // Original
+ int main(int argc, char **argv)
+ {
+ QApplication app(argc, argv);
+
+ MyMainWidget mmw;
+
+ mmw.show();
+ return app.exec();
+ }
+
+ // Single instance
+ int main(int argc, char **argv)
+ {
+ QtSingleApplication app(argc, argv);
+
+ if (app.isRunning())
+ return 0;
+
+ MyMainWidget mmw;
+
+ app.setActivationWindow(&mmw);
+
+ mmw.show();
+ return app.exec();
+ }
+ \endcode
+
+ Once this QtSingleApplication instance is destroyed(for example,
+ when the user quits), when the user next attempts to run the
+ application this instance will not, of course, be encountered. The
+ next instance to call isRunning() or sendMessage() will assume the
+ role as the new running instance.
+
+ For console (non-GUI) applications, QtSingleCoreApplication may be
+ used instead of this class, to avoid the dependency on the QtGui
+ library.
+
+ \sa QtSingleCoreApplication
+*/
+
+
+void QtSingleApplication::sysInit(const QString &appId)
+{
+ actWin = 0;
+ peer = new QtLocalPeer(this, appId);
+ connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
+}
+
+
+/*!
+ Creates a QtSingleApplication object. The application identifier
+ will be QCoreApplication::applicationFilePath(). \a argc, \a
+ argv, and \a GUIenabled are passed on to the QAppliation constructor.
+
+ If you are creating a console application (i.e. setting \a
+ GUIenabled to false), you may consider using
+ QtSingleCoreApplication instead.
+*/
+
+QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled)
+ : QApplication(argc, argv, GUIenabled)
+{
+ sysInit();
+}
+
+
+/*!
+ Creates a QtSingleApplication object with the application
+ identifier \a appId. \a argc and \a argv are passed on to the
+ QAppliation constructor.
+*/
+
+QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv)
+ : QApplication(argc, argv)
+{
+ sysInit(appId);
+}
+
+
+/*!
+ Creates a QtSingleApplication object. The application identifier
+ will be QCoreApplication::applicationFilePath(). \a argc, \a
+ argv, and \a type are passed on to the QAppliation constructor.
+*/
+QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type)
+ : QApplication(argc, argv, type)
+{
+ sysInit();
+}
+
+
+#if defined(Q_WS_X11)
+/*!
+ Special constructor for X11, ref. the documentation of
+ QApplication's corresponding constructor. The application identifier
+ will be QCoreApplication::applicationFilePath(). \a dpy, \a visual,
+ and \a cmap are passed on to the QApplication constructor.
+*/
+QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap)
+ : QApplication(dpy, visual, cmap)
+{
+ sysInit();
+}
+
+/*!
+ Special constructor for X11, ref. the documentation of
+ QApplication's corresponding constructor. The application identifier
+ will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a
+ argv, \a visual, and \a cmap are passed on to the QApplication
+ constructor.
+*/
+QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
+ : QApplication(dpy, argc, argv, visual, cmap)
+{
+ sysInit();
+}
+
+/*!
+ Special constructor for X11, ref. the documentation of
+ QApplication's corresponding constructor. The application identifier
+ will be \a appId. \a dpy, \a argc, \a
+ argv, \a visual, and \a cmap are passed on to the QApplication
+ constructor.
+*/
+QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap)
+ : QApplication(dpy, argc, argv, visual, cmap)
+{
+ sysInit(appId);
+}
+#endif
+
+
+/*!
+ Returns true if another instance of this application is running;
+ otherwise false.
+
+ This function does not find instances of this application that are
+ being run by a different user (on Windows: that are running in
+ another session).
+
+ \sa sendMessage()
+*/
+
+bool QtSingleApplication::isRunning()
+{
+ return peer->isClient();
+}
+
+
+/*!
+ Tries to send the text \a message to the currently running
+ instance. The QtSingleApplication object in the running instance
+ will emit the messageReceived() signal when it receives the
+ message.
+
+ This function returns true if the message has been sent to, and
+ processed by, the current instance. If there is no instance
+ currently running, or if the running instance fails to process the
+ message within \a timeout milliseconds, this function return false.
+
+ \sa isRunning(), messageReceived()
+*/
+bool QtSingleApplication::sendMessage(const QString &message, int timeout)
+{
+ return peer->sendMessage(message, timeout);
+}
+
+
+/*!
+ Returns the application identifier. Two processes with the same
+ identifier will be regarded as instances of the same application.
+*/
+QString QtSingleApplication::id() const
+{
+ return peer->applicationId();
+}
+
+
+/*!
+ Sets the activation window of this application to \a aw. The
+ activation window is the widget that will be activated by
+ activateWindow(). This is typically the application's main window.
+
+ If \a activateOnMessage is true (the default), the window will be
+ activated automatically every time a message is received, just prior
+ to the messageReceived() signal being emitted.
+
+ \sa activateWindow(), messageReceived()
+*/
+
+void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage)
+{
+ actWin = aw;
+ if (activateOnMessage)
+ connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
+ else
+ disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
+}
+
+
+/*!
+ Returns the applications activation window if one has been set by
+ calling setActivationWindow(), otherwise returns 0.
+
+ \sa setActivationWindow()
+*/
+QWidget* QtSingleApplication::activationWindow() const
+{
+ return actWin;
+}
+
+
+/*!
+ De-minimizes, raises, and activates this application's activation window.
+ This function does nothing if no activation window has been set.
+
+ This is a convenience function to show the user that this
+ application instance has been activated when he has tried to start
+ another instance.
+
+ This function should typically be called in response to the
+ messageReceived() signal. By default, that will happen
+ automatically, if an activation window has been set.
+
+ \sa setActivationWindow(), messageReceived(), initialize()
+*/
+void QtSingleApplication::activateWindow()
+{
+ if (actWin) {
+ actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized);
+ actWin->raise();
+ actWin->activateWindow();
+ }
+}
+
+
+/*!
+ \fn void QtSingleApplication::messageReceived(const QString& message)
+
+ This signal is emitted when the current instance receives a \a
+ message from another instance of this application.
+
+ \sa sendMessage(), setActivationWindow(), activateWindow()
+*/
+
+
+/*!
+ \fn void QtSingleApplication::initialize(bool dummy = true)
+
+ \obsolete
+*/
--- /dev/null
+/****************************************************************************
+**
+** This file is part of a Qt Solutions component.
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Solutions Commercial License Agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** Please note Third Party Software included with Qt Solutions may impose
+** additional restrictions and it is the user's responsibility to ensure
+** that they have met the licensing requirements of the GPL, LGPL, or Qt
+** Solutions Commercial license and the relevant license of the Third
+** Party Software they are using.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+
+#include <QtGui/QApplication>
+
+class QtLocalPeer;
+
+#if defined(Q_WS_WIN)
+# if !defined(QT_QTSINGLEAPPLICATION_EXPORT) && !defined(QT_QTSINGLEAPPLICATION_IMPORT)
+# define QT_QTSINGLEAPPLICATION_EXPORT
+# elif defined(QT_QTSINGLEAPPLICATION_IMPORT)
+# if defined(QT_QTSINGLEAPPLICATION_EXPORT)
+# undef QT_QTSINGLEAPPLICATION_EXPORT
+# endif
+# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllimport)
+# elif defined(QT_QTSINGLEAPPLICATION_EXPORT)
+# undef QT_QTSINGLEAPPLICATION_EXPORT
+# define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllexport)
+# endif
+#else
+# define QT_QTSINGLEAPPLICATION_EXPORT
+#endif
+
+class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication
+{
+ Q_OBJECT
+
+public:
+ QtSingleApplication(int &argc, char **argv, bool GUIenabled = true);
+ QtSingleApplication(const QString &id, int &argc, char **argv);
+ QtSingleApplication(int &argc, char **argv, Type type);
+#if defined(Q_WS_X11)
+ QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
+ QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0);
+ QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
+#endif
+
+ bool isRunning();
+ QString id() const;
+
+ void setActivationWindow(QWidget* aw, bool activateOnMessage = true);
+ QWidget* activationWindow() const;
+
+ // Obsolete:
+ void initialize(bool dummy = true)
+ { isRunning(); Q_UNUSED(dummy) }
+
+public Q_SLOTS:
+ bool sendMessage(const QString &message, int timeout = 5000);
+ void activateWindow();
+
+
+Q_SIGNALS:
+ void messageReceived(const QString &message);
+
+
+private:
+ void sysInit(const QString &appId = QString());
+ QtLocalPeer *peer;
+ QWidget *actWin;
+};
--- /dev/null
+INCLUDEPATH += $$PWD
+DEPENDPATH += $$PWD
+QT *= network
+
+qtsingleapplication-uselib:!qtsingleapplication-buildlib {
+ LIBS += -L$$QTSINGLEAPPLICATION_LIBDIR -l$$QTSINGLEAPPLICATION_LIBNAME
+} else {
+ SOURCES += $$PWD/qtsingleapplication.cpp $$PWD/qtlocalpeer.cpp
+ HEADERS += $$PWD/qtsingleapplication.h $$PWD/qtlocalpeer.h
+}
+
+win32 {
+ contains(TEMPLATE, lib):contains(CONFIG, shared):DEFINES += QT_QTSINGLEAPPLICATION_EXPORT
+ else:qtsingleapplication-uselib:DEFINES += QT_QTSINGLEAPPLICATION_IMPORT
+}
--- /dev/null
+/****************************************************************************
+**
+** This file is part of a Qt Solutions component.
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Solutions Commercial License Agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** Please note Third Party Software included with Qt Solutions may impose
+** additional restrictions and it is the user's responsibility to ensure
+** that they have met the licensing requirements of the GPL, LGPL, or Qt
+** Solutions Commercial license and the relevant license of the Third
+** Party Software they are using.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+
+#include "qtsinglecoreapplication.h"
+#include "qtlocalpeer.h"
+
+/*!
+ \class QtSingleCoreApplication qtsinglecoreapplication.h
+ \brief A variant of the QtSingleApplication class for non-GUI applications.
+
+ This class is a variant of QtSingleApplication suited for use in
+ console (non-GUI) applications. It is an extension of
+ QCoreApplication (instead of QApplication). It does not require
+ the QtGui library.
+
+ The API and usage is identical to QtSingleApplication, except that
+ functions relating to the "activation window" are not present, for
+ obvious reasons. Please refer to the QtSingleApplication
+ documentation for explanation of the usage.
+
+ A QtSingleCoreApplication instance can communicate to a
+ QtSingleApplication instance if they share the same application
+ id. Hence, this class can be used to create a light-weight
+ command-line tool that sends commands to a GUI application.
+
+ \sa QtSingleApplication
+*/
+
+/*!
+ Creates a QtSingleCoreApplication object. The application identifier
+ will be QCoreApplication::applicationFilePath(). \a argc and \a
+ argv are passed on to the QCoreAppliation constructor.
+*/
+
+QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv)
+ : QCoreApplication(argc, argv)
+{
+ peer = new QtLocalPeer(this);
+ connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
+}
+
+
+/*!
+ Creates a QtSingleCoreApplication object with the application
+ identifier \a appId. \a argc and \a argv are passed on to the
+ QCoreAppliation constructor.
+*/
+QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv)
+ : QCoreApplication(argc, argv)
+{
+ peer = new QtLocalPeer(this, appId);
+ connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
+}
+
+
+/*!
+ Returns true if another instance of this application is running;
+ otherwise false.
+
+ This function does not find instances of this application that are
+ being run by a different user (on Windows: that are running in
+ another session).
+
+ \sa sendMessage()
+*/
+
+bool QtSingleCoreApplication::isRunning()
+{
+ return peer->isClient();
+}
+
+
+/*!
+ Tries to send the text \a message to the currently running
+ instance. The QtSingleCoreApplication object in the running instance
+ will emit the messageReceived() signal when it receives the
+ message.
+
+ This function returns true if the message has been sent to, and
+ processed by, the current instance. If there is no instance
+ currently running, or if the running instance fails to process the
+ message within \a timeout milliseconds, this function return false.
+
+ \sa isRunning(), messageReceived()
+*/
+
+bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout)
+{
+ return peer->sendMessage(message, timeout);
+}
+
+
+/*!
+ Returns the application identifier. Two processes with the same
+ identifier will be regarded as instances of the same application.
+*/
+
+QString QtSingleCoreApplication::id() const
+{
+ return peer->applicationId();
+}
+
+
+/*!
+ \fn void QtSingleCoreApplication::messageReceived(const QString& message)
+
+ This signal is emitted when the current instance receives a \a
+ message from another instance of this application.
+
+ \sa sendMessage()
+*/
--- /dev/null
+/****************************************************************************
+**
+** This file is part of a Qt Solutions component.
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Solutions Commercial License Agreement provided
+** with the Software or, alternatively, in accordance with the terms
+** contained in a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** Please note Third Party Software included with Qt Solutions may impose
+** additional restrictions and it is the user's responsibility to ensure
+** that they have met the licensing requirements of the GPL, LGPL, or Qt
+** Solutions Commercial license and the relevant license of the Third
+** Party Software they are using.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+
+#include <QtCore/QCoreApplication>
+
+class QtLocalPeer;
+
+class QtSingleCoreApplication : public QCoreApplication
+{
+ Q_OBJECT
+
+public:
+ QtSingleCoreApplication(int &argc, char **argv);
+ QtSingleCoreApplication(const QString &id, int &argc, char **argv);
+
+ bool isRunning();
+ QString id() const;
+
+public Q_SLOTS:
+ bool sendMessage(const QString &message, int timeout = 5000);
+
+
+Q_SIGNALS:
+ void messageReceived(const QString &message);
+
+
+private:
+ QtLocalPeer* peer;
+};
--- /dev/null
+INCLUDEPATH += $$PWD
+DEPENDPATH += $$PWD
+HEADERS += $$PWD/qtsinglecoreapplication.h $$PWD/qtlocalpeer.h
+SOURCES += $$PWD/qtsinglecoreapplication.cpp $$PWD/qtlocalpeer.cpp
+
+QT *= network
+
+win32:contains(TEMPLATE, lib):contains(CONFIG, shared) {
+ DEFINES += QT_QTSINGLECOREAPPLICATION_EXPORT=__declspec(dllexport)
+}
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License versions 2.0 or 3.0 as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information
+** to ensure GNU General Public Licensing requirements will be met:
+** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
+** http://www.gnu.org/copyleft/gpl.html. In addition, as a special
+** exception, Nokia gives you certain additional rights. These rights
+** are described in the Nokia Qt GPL Exception version 1.3, included in
+** the file GPL_EXCEPTION.txt in this package.
+**
+** Qt for Windows(R) Licensees
+** As a special exception, Nokia, as the sole copyright holder for Qt
+** Designer, grants users of the Qt/Eclipse Integration plug-in the
+** right for the Qt/Eclipse Integration to link to functionality
+** provided by Qt Designer and its related libraries.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+#include "searchlineedit.h"
+
+#include <QtGui/QPainter>
+#include <QtGui/QMouseEvent>
+#include <QtGui/QMenu>
+#include <QtGui/QStyle>
+#include <QtGui/QStyleOptionFrameV2>
+
+ClearButton::ClearButton(QWidget *parent)
+ : QAbstractButton(parent)
+{
+ setCursor(Qt::ArrowCursor);
+ setToolTip(tr("Clear"));
+ setVisible(false);
+ setFocusPolicy(Qt::NoFocus);
+}
+
+void ClearButton::paintEvent(QPaintEvent *event)
+{
+ Q_UNUSED(event);
+ QPainter painter(this);
+ int height = this->height();
+
+ painter.setRenderHint(QPainter::Antialiasing, true);
+ QColor color = palette().color(QPalette::Mid);
+ painter.setBrush(isDown()
+ ? palette().color(QPalette::Dark)
+ : palette().color(QPalette::Mid));
+ painter.setPen(painter.brush().color());
+ int size = width();
+ int offset = size / 3.5;
+ int radius = size - offset * 2;
+ painter.drawEllipse(offset, offset, radius, radius);
+
+ painter.setPen(QPen(palette().color(QPalette::Base),2));
+ int border = offset * 1.6;
+ painter.drawLine(border, border, width() - border, height - border);
+ painter.drawLine(border, height - border, width() - border, border);
+}
+
+void ClearButton::textChanged(const QString &text)
+{
+ setVisible(!text.isEmpty());
+}
+
+/*
+ Search icon on the left hand side of the search widget
+ When a menu is set a down arrow appears
+ */
+class SearchButton : public QAbstractButton {
+public:
+ SearchButton(QWidget *parent = 0);
+ void paintEvent(QPaintEvent *event);
+ QMenu *m_menu;
+
+protected:
+ void mousePressEvent(QMouseEvent *event);
+};
+
+SearchButton::SearchButton(QWidget *parent)
+ : QAbstractButton(parent),
+ m_menu(0)
+{
+ setObjectName(QLatin1String("SearchButton"));
+ setCursor(Qt::ArrowCursor);
+ setFocusPolicy(Qt::NoFocus);
+}
+
+void SearchButton::mousePressEvent(QMouseEvent *event)
+{
+ if (m_menu && event->button() == Qt::LeftButton) {
+ QWidget *p = parentWidget();
+ if (p) {
+ QPoint r = p->mapToGlobal(QPoint(0, p->height()));
+ m_menu->exec(QPoint(r.x() + height() / 2, r.y()));
+ }
+ event->accept();
+ }
+ QAbstractButton::mousePressEvent(event);
+}
+
+void SearchButton::paintEvent(QPaintEvent *event)
+{
+ Q_UNUSED(event);
+ QPainterPath myPath;
+
+ int radius = (height() / 5) * 2;
+ QRect circle(height() / 5.5, height() / 3.5, radius, radius);
+ myPath.addEllipse(circle);
+
+ myPath.arcMoveTo(circle, 300);
+ QPointF c = myPath.currentPosition();
+ int diff = height() / 6;
+ myPath.lineTo(qMin(width() - 2, (int)c.x() + diff), c.y() + diff);
+
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing, true);
+ painter.setPen(QPen(Qt::darkGray, 2));
+ painter.drawPath(myPath);
+
+ if (m_menu) {
+ QPainterPath dropPath;
+ dropPath.arcMoveTo(circle, 320);
+ QPointF c = dropPath.currentPosition();
+ c = QPointF(c.x() + 3.5, c.y() + 0.5);
+ dropPath.moveTo(c);
+ dropPath.lineTo(c.x() + 4, c.y());
+ dropPath.lineTo(c.x() + 2, c.y() + 2);
+ dropPath.closeSubpath();
+ painter.setPen(Qt::darkGray);
+ painter.setBrush(Qt::darkGray);
+ painter.setRenderHint(QPainter::Antialiasing, false);
+ painter.drawPath(dropPath);
+ }
+ painter.end();
+}
+
+/*
+ SearchLineEdit is an enhanced QLineEdit
+ - A Search icon on the left with optional menu
+ - When there is no text and doesn't have focus an "inactive text" is displayed
+ - When there is text a clear button is displayed on the right hand side
+ */
+SearchLineEdit::SearchLineEdit(QWidget *parent) : ExLineEdit(parent),
+ m_searchButton(new SearchButton(this))
+{
+ connect(lineEdit(), SIGNAL(textChanged(const QString &)),
+ this, SIGNAL(textChanged(const QString &)));
+
+ connect(lineEdit(), SIGNAL(returnPressed()),
+ this, SLOT(returnPressed()));
+
+ setLeftWidget(m_searchButton);
+ m_inactiveText = tr("Search");
+
+ QSizePolicy policy = sizePolicy();
+ setSizePolicy(QSizePolicy::Preferred, policy.verticalPolicy());
+}
+
+void SearchLineEdit::paintEvent(QPaintEvent *event)
+{
+ if (lineEdit()->text().isEmpty() && !hasFocus() && !m_inactiveText.isEmpty()) {
+ ExLineEdit::paintEvent(event);
+ QStyleOptionFrameV2 panel;
+ initStyleOption(&panel);
+ QRect r = style()->subElementRect(QStyle::SE_LineEditContents, &panel, this);
+ QFontMetrics fm = fontMetrics();
+ int horizontalMargin = lineEdit()->x();
+ QRect lineRect(horizontalMargin + r.x(), r.y() + (r.height() - fm.height() + 1) / 2,
+ r.width() - 2 * horizontalMargin, fm.height());
+ QPainter painter(this);
+ painter.setPen(palette().brush(QPalette::Disabled, QPalette::Text).color());
+ painter.drawText(lineRect, Qt::AlignLeft|Qt::AlignVCenter, m_inactiveText);
+ } else {
+ ExLineEdit::paintEvent(event);
+ }
+}
+
+void SearchLineEdit::resizeEvent(QResizeEvent *event)
+{
+ updateGeometries();
+ ExLineEdit::resizeEvent(event);
+}
+
+void SearchLineEdit::updateGeometries()
+{
+ int menuHeight = height();
+ int menuWidth = menuHeight + 1;
+ if (!m_searchButton->m_menu)
+ menuWidth = (menuHeight / 5) * 4;
+ m_searchButton->resize(QSize(menuWidth, menuHeight));
+}
+
+QString SearchLineEdit::inactiveText() const
+{
+ return m_inactiveText;
+}
+
+void SearchLineEdit::setInactiveText(const QString &text)
+{
+ m_inactiveText = text;
+}
+
+void SearchLineEdit::setMenu(QMenu *menu)
+{
+ if (m_searchButton->m_menu)
+ m_searchButton->m_menu->deleteLater();
+ m_searchButton->m_menu = menu;
+ updateGeometries();
+}
+
+QMenu *SearchLineEdit::menu() const
+{
+ if (!m_searchButton->m_menu) {
+ m_searchButton->m_menu = new QMenu(m_searchButton);
+ if (isVisible())
+ (const_cast<SearchLineEdit*>(this))->updateGeometries();
+ }
+ return m_searchButton->m_menu;
+}
+
+void SearchLineEdit::returnPressed()
+{
+ emit search(lineEdit()->text());
+}
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License versions 2.0 or 3.0 as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information
+** to ensure GNU General Public Licensing requirements will be met:
+** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
+** http://www.gnu.org/copyleft/gpl.html. In addition, as a special
+** exception, Nokia gives you certain additional rights. These rights
+** are described in the Nokia Qt GPL Exception version 1.3, included in
+** the file GPL_EXCEPTION.txt in this package.
+**
+** Qt for Windows(R) Licensees
+** As a special exception, Nokia, as the sole copyright holder for Qt
+** Designer, grants users of the Qt/Eclipse Integration plug-in the
+** right for the Qt/Eclipse Integration to link to functionality
+** provided by Qt Designer and its related libraries.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+#ifndef SEARCHLINEEDIT_H
+#define SEARCHLINEEDIT_H
+
+#include "urllineedit.h"
+
+#include <QtGui/QLineEdit>
+#include <QtGui/QAbstractButton>
+
+QT_BEGIN_NAMESPACE
+class QMenu;
+QT_END_NAMESPACE
+
+class SearchButton;
+
+/*
+ Clear button on the right hand side of the search widget.
+ Hidden by default
+ "A circle with an X in it"
+ */
+class ClearButton : public QAbstractButton
+{
+ Q_OBJECT
+
+public:
+ ClearButton(QWidget *parent = 0);
+ void paintEvent(QPaintEvent *event);
+
+public slots:
+ void textChanged(const QString &text);
+};
+
+
+class SearchLineEdit : public ExLineEdit
+{
+ Q_OBJECT
+ Q_PROPERTY(QString inactiveText READ inactiveText WRITE setInactiveText)
+
+signals:
+ void textChanged(const QString &text);
+ void search(const QString &text);
+
+public:
+ SearchLineEdit(QWidget *parent = 0);
+
+ QString inactiveText() const;
+ void setInactiveText(const QString &text);
+
+ QMenu *menu() const;
+ void setMenu(QMenu *menu);
+ void updateGeometries();
+
+protected:
+ void resizeEvent(QResizeEvent *event);
+ void paintEvent(QPaintEvent *event);
+
+private slots:
+ void returnPressed();
+
+private:
+
+ SearchButton *m_searchButton;
+ QString m_inactiveText;
+};
+
+#endif // SEARCHLINEEDIT_H
+
--- /dev/null
+#include "searchparams.h"
+
+SearchParams::SearchParams() {
+ m_sortBy = SortByRelevance;
+}
--- /dev/null
+#ifndef SEARCHPARAMS_H
+#define SEARCHPARAMS_H
+
+#include <QObject>
+
+
+
+class SearchParams : public QObject {
+
+public:
+ SearchParams();
+
+ const QString keywords() const { return m_keywords; }
+ void setKeywords( QString keywords ) { m_keywords = keywords; }
+
+ int sortBy() const { return m_sortBy; }
+ void setSortBy( int sortBy ) { m_sortBy = sortBy; }
+
+ enum SortBy {
+ SortByRelevance = 1,
+ SortByNewest,
+ SortByViewCount
+ };
+
+private:
+ QString m_keywords;
+ int m_sortBy;
+
+};
+
+#endif // SEARCHPARAMS_H
--- /dev/null
+#include "spacer.h"
+
+Spacer::Spacer(QWidget *parent, QWidget *child) : QWidget(parent) {
+ QBoxLayout *layout = new QHBoxLayout();
+ layout->addWidget(child);
+ setLayout(layout);
+}
--- /dev/null
+#ifndef SPACER_H
+#define SPACER_H
+
+#include <QtGui>
+
+class Spacer : public QWidget
+{
+public:
+ Spacer(QWidget *parent, QWidget *child);
+};
+
+#endif // SPACER_H
--- /dev/null
+/*
+ * This is an adaptation of Jani Huhtanen Exponential blur code.
+ *
+ * Copyright 2007 Jani Huhtanen <jani.huhtanen@tut.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License version 2 as
+ * published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <QImage>
+#include <cmath>
+
+#include "imageblur.h"
+
+/* ============================================================================
+ * PUBLIC Constructor/Destructors
+ */
+ImageBlur::ImageBlur (QImage *image, int aprec, int zprec) {
+ m_image = image;
+ m_aprec = aprec;
+ m_zprec = zprec;
+}
+
+ImageBlur::~ImageBlur() {
+ m_image = NULL;
+}
+
+/* ============================================================================
+ * PUBLIC STATIC Methods
+ */
+void ImageBlur::expblur (QImage *image, int aprec, int zprec, int radius) {
+ ImageBlur imageBlur(image, aprec, zprec);
+ imageBlur.expblur(radius);
+}
+
+/* ============================================================================
+ * PUBLIC Methods
+ */
+void ImageBlur::expblur (int radius) {
+ if (radius < 1)
+ return;
+
+ /* Calculate the alpha such that 90% of
+ * the kernel is within the radius.
+ * (Kernel extends to infinity)
+ */
+ int alpha = (int)((1 << m_aprec) * (1.0f - std::exp(-2.3f / (radius + 1.f))));
+
+ for (int row = 0; row < m_image->height(); ++row)
+ blurrow(row, alpha);
+
+ for (int col = 0; col < m_image->width(); ++col)
+ blurcol(col, alpha);
+}
+
+/* ============================================================================
+ * PRIVATE Methods
+ */
+void ImageBlur::blurcol (int col, int alpha) {
+ int zR, zG, zB, zA;
+
+ QRgb *ptr = (QRgb *)m_image->bits();
+ ptr += col;
+
+ zR = *((unsigned char *)ptr ) << m_zprec;
+ zG = *((unsigned char *)ptr + 1) << m_zprec;
+ zB = *((unsigned char *)ptr + 2) << m_zprec;
+ zA = *((unsigned char *)ptr + 3) << m_zprec;
+
+ for (int index = m_image->width();
+ index < (m_image->height() - 1) * m_image->width();
+ index += m_image->width())
+ {
+ blurinner((unsigned char *)&ptr[index], zR, zG, zB, zA, alpha);
+ }
+
+ for (int index = (m_image->height() - 2) * m_image->width();
+ index >= 0;
+ index -= m_image->width())
+ {
+ blurinner((unsigned char *)&ptr[index], zR, zG, zB, zA, alpha);
+ }
+}
+
+void ImageBlur::blurrow (int line, int alpha) {
+ int zR, zG, zB, zA;
+
+ QRgb *ptr = (QRgb *)m_image->scanLine(line);
+
+ zR = *((unsigned char *)ptr ) << m_zprec;
+ zG = *((unsigned char *)ptr + 1) << m_zprec;
+ zB = *((unsigned char *)ptr + 2) << m_zprec;
+ zA = *((unsigned char *)ptr + 3) << m_zprec;
+
+ for (int index = 1; index < m_image->width(); ++index)
+ blurinner((unsigned char *)&ptr[index], zR, zG, zB, zA, alpha);
+
+ for (int index = m_image->width() - 2; index >= 0; --index)
+ blurinner((unsigned char *)&ptr[index], zR, zG, zB, zA, alpha);
+}
+
+void ImageBlur::blurinner ( unsigned char *bptr,
+ int &zR, int &zG, int &zB, int &zA,
+ int alpha)
+{
+ int R, G, B, A;
+ R = *bptr;
+ G = *(bptr + 1);
+ B = *(bptr + 2);
+ A = *(bptr + 3);
+
+ zR += (alpha * ((R << m_zprec) - zR)) >> m_aprec;
+ zG += (alpha * ((G << m_zprec) - zG)) >> m_aprec;
+ zB += (alpha * ((B << m_zprec) - zB)) >> m_aprec;
+ zA += (alpha * ((A << m_zprec) - zA)) >> m_aprec;
+
+ *bptr = zR >> m_zprec;
+ *(bptr+1) = zG >> m_zprec;
+ *(bptr+2) = zB >> m_zprec;
+ *(bptr+3) = zA >> m_zprec;
+}
+
--- /dev/null
+/*
+ * This is an adaptation of Jani Huhtanen Exponential blur code.
+ *
+ * Copyright 2007 Jani Huhtanen <jani.huhtanen@tut.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License version 2 as
+ * published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _KDE_PLASMA_BLUR_H_
+#define _KDE_PLASMA_BLUR_H_
+
+class QImage;
+
+class ImageBlur {
+ public:
+ ImageBlur (QImage *image, int aprec, int zprec);
+ ~ImageBlur();
+
+ public:
+ void expblur (int radius);
+
+ public:
+ static void expblur (QImage *image, int aprec, int zprec, int radius);
+
+ private:
+ void blurcol (int col, int alpha);
+ void blurrow (int line, int alpha);
+
+ void blurinner (unsigned char *bptr,
+ int &zR, int &zG, int &zB, int &zA,
+ int alpha);
+
+ private:
+ QImage *m_image;
+ int m_aprec;
+ int m_zprec;
+};
+
+#endif /* !_KDE_PLASMA_BLUR_H_ */
+
--- /dev/null
+#include "thaction.h"
+
+/* ============================================================================
+ * PRIVATE Class
+ */
+class THAction::Private {
+ public:
+ bool isHovered;
+ bool isChecked;
+ QString text;
+ QIcon icon;
+};
+
+/* ============================================================================
+ * PUBLIC Constructor/Destructors
+ */
+THAction::THAction (QObject *parent)
+ : QObject(parent), d(new THAction::Private)
+{
+ d->isHovered = false;
+ d->isChecked = false;
+}
+
+THAction::THAction (const QString& text, QObject *parent)
+ : QObject(parent), d(new THAction::Private)
+{
+ d->isHovered = false;
+ d->isChecked = false;
+ d->text = text;
+}
+
+THAction::THAction (const QIcon& icon, const QString& text, QObject *parent)
+ : QObject(parent), d(new THAction::Private)
+{
+ d->isHovered = false;
+ d->isChecked = false;
+ d->icon = icon;
+ d->text = text;
+}
+
+THAction::~THAction() {
+ delete d;
+}
+
+/* ============================================================================
+ * PUBLIC Properties
+ */
+bool THAction::isChecked (void) const {
+ return(d->isChecked);
+}
+
+bool THAction::isHovered (void) const {
+ return(d->isHovered);
+}
+
+QIcon THAction::icon (void) const {
+ return(d->icon);
+}
+
+void THAction::setIcon (const QIcon& icon) {
+ d->icon = icon;
+}
+
+
+QString THAction::text (void) const {
+ return(d->text);
+}
+
+void THAction::setText (const QString& text) {
+ d->text = text;
+}
+
+/* ============================================================================
+ * PUBLIC SLOTS
+ */
+void THAction::hover (bool isHovered) {
+ d->isHovered = isHovered;
+ if (d->isHovered) emit hovered();
+}
+
+void THAction::toggle (void) {
+ emit toggled(d->isChecked);
+}
+
+void THAction::trigger (void) {
+ emit triggered(d->isChecked);
+}
+
+void THAction::setChecked (bool checked) {
+ d->isChecked = checked;
+}
--- /dev/null
+#ifndef _THACTION_H_
+#define _THACTION_H_
+
+#include <QObject>
+#include <QIcon>
+
+class THAction : public QObject {
+ Q_OBJECT
+
+ Q_PROPERTY(QIcon icon READ icon WRITE setIcon)
+ Q_PROPERTY(QString text READ text WRITE setText)
+
+ public:
+ THAction (QObject *parent = 0);
+ THAction (const QString& text, QObject *parent = 0);
+ THAction (const QIcon& icon, const QString& text, QObject *parent = 0);
+ ~THAction();
+
+ signals:
+ void triggered (bool checked = false);
+ void toggled (bool checked);
+ void hovered (void);
+
+ public:
+ bool isChecked (void) const;
+ bool isHovered (void) const;
+
+ QIcon icon (void) const;
+ void setIcon (const QIcon& icon);
+
+ QString text (void) const;
+ void setText (const QString& text);
+
+ public slots:
+ void toggle (void);
+ void trigger (void);
+ void hover (bool isHovered = false);
+ void setChecked (bool checked);
+
+ private:
+ class Private;
+ Private *d;
+};
+
+#endif /* !_THACTION_H_ */
+
--- /dev/null
+#include <QList>
+
+#include "thactiongroup.h"
+#include "thaction.h"
+
+/* ============================================================================
+ * PRIVATE Class
+ */
+class THActionGroup::Private {
+ public:
+ QList<THAction *> actionList;
+ QString name;
+};
+
+/* ============================================================================
+ * PUBLIC Constructor/Destructors
+ */
+THActionGroup::THActionGroup (QObject *parent)
+ : QObject(parent), d(new THActionGroup::Private)
+{
+}
+
+THActionGroup::THActionGroup (const QString& name, QObject *parent)
+ : QObject(parent), d(new THActionGroup::Private)
+{
+ d->name = name;
+}
+
+THActionGroup::~THActionGroup() {
+ delete d;
+}
+
+/* ============================================================================
+ * PUBLIC Methods
+ */
+THAction *THActionGroup::addAction (THAction *action) {
+ d->actionList.append(action);
+ return(action);
+}
+
+THAction *THActionGroup::addAction (const QString& text) {
+ THAction *action = new THAction(text, this);
+ d->actionList.append(action);
+ return(action);
+}
+
+THAction *THActionGroup::addAction (const QIcon& icon, const QString& text) {
+ THAction *action = new THAction(icon, text, this);
+ d->actionList.append(action);
+ return(action);
+}
+
+/* ============================================================================
+ * PUBLIC Properties
+ */
+QString THActionGroup::name (void) const {
+ return(d->name);
+}
+
+void THActionGroup::setName (const QString& name) {
+ d->name = name;
+}
+
--- /dev/null
+#ifndef _THACTIONGROUP_H_
+#define _THACTIONGROUP_H_
+
+#include <QObject>
+#include <QIcon>
+class THAction;
+
+class THActionGroup : public QObject {
+ Q_OBJECT
+
+ public:
+ THActionGroup (QObject *parent = 0);
+ THActionGroup (const QString& name, QObject *parent = 0);
+ ~THActionGroup();
+
+ public:
+ THAction *addAction (THAction *action);
+ THAction *addAction (const QString& text);
+ THAction *addAction (const QIcon& icon, const QString& text);
+
+ QString name (void) const;
+ void setName (const QString& name);
+
+ private:
+ class Private;
+ Private *d;
+};
+
+#endif /* !_THACTIONGROUP_H_ */
+
--- /dev/null
+#include <QPaintEvent>
+#include <QList>
+
+#include "thblackbutton.h"
+#include "thblackbar.h"
+#include "thpainter.h"
+#include "thaction.h"
+
+/* ============================================================================
+ * PRIVATE Class
+ */
+class THBlackBar::Private {
+ public:
+ QList<THAction *> actionList;
+ THAction *checkedAction;
+ THAction *hoveredAction;
+};
+
+/* ============================================================================
+ * PUBLIC Constructor/Destructors
+ */
+THBlackBar::THBlackBar (QWidget *parent)
+ : QWidget(parent), d(new THBlackBar::Private)
+{
+ // Setup Widget Options
+ setMouseTracking(true);
+
+ // Setup Members
+ d->hoveredAction = NULL;
+ d->checkedAction = NULL;
+}
+
+THBlackBar::~THBlackBar() {
+ delete d;
+}
+
+/* ============================================================================
+ * PUBLIC Methods
+ */
+THAction *THBlackBar::addAction (THAction *action) {
+ d->actionList.append(action);
+ return(action);
+}
+
+THAction *THBlackBar::addAction (const QString& text) {
+ THAction *action = new THAction(text, this);
+ d->actionList.append(action);
+ return(action);
+}
+
+void THBlackBar::setCheckedAction(int index) {
+ if (d->checkedAction)
+ d->checkedAction->setChecked(false);
+ d->checkedAction = d->actionList.at(index);
+ d->checkedAction->setChecked(true);
+ update();
+}
+
+QSize THBlackBar::minimumSizeHint (void) const {
+ int itemsWidth = calculateButtonWidth() * d->actionList.size();
+ return(QSize(100 + itemsWidth, 32));
+}
+
+/* ============================================================================
+ * PROTECTED Methods
+ */
+void THBlackBar::paintEvent (QPaintEvent *event) {
+ int height = event->rect().height();
+ int width = event->rect().width();
+ // int mh = (height / 2);
+
+ // THPainter p(this);
+ QPainter p(this);
+
+ /*
+ // Draw Background
+ QLinearGradient linearGradUp(QPointF(0, 0), QPointF(0, mh));
+ linearGradUp.setColorAt(0, QColor(0x97, 0x97, 0x97));
+ linearGradUp.setColorAt(1, QColor(0x4d, 0x4d, 0x4d));
+ p.fillRect(0, 0, width, mh, QBrush(linearGradUp));
+
+ QLinearGradient linearGradDw(QPointF(0, mh), QPointF(0, height));
+ linearGradDw.setColorAt(0, QColor(0x3a, 0x3a, 0x3a));
+ linearGradDw.setColorAt(1, QColor(0x42, 0x42, 0x42));
+ p.fillRect(0, mh, width, mh, QBrush(linearGradDw));
+ */
+
+ // Calculate Buttons Size & Location
+ int buttonWidth = width / d->actionList.size(); // calculateButtonWidth();
+ // int buttonsWidth = width; // buttonWidth * d->actionList.size();
+ int buttonsX = 0; // (width / 2) - (buttonsWidth / 2);
+
+ // Draw Buttons
+ // p.translate(0, 4);
+ QRect rect(buttonsX, 0, buttonWidth, height);
+ foreach (THAction *action, d->actionList) {
+ drawButton(&p, rect, action);
+ rect.moveLeft(rect.x() + rect.width());
+ }
+ // p.translate(0, -4);
+
+ // Draw Buttons Shadow
+ // p.fillRect(buttonsX, height - 4, buttonsWidth, 1, QColor(0x6d, 0x6d, 0x6d));
+
+ p.end();
+}
+
+void THBlackBar::mouseMoveEvent (QMouseEvent *event) {
+ QWidget::mouseMoveEvent(event);
+
+ THAction *action = hoveredAction(event->pos());
+ if (action == NULL && d->hoveredAction != NULL) {
+ d->hoveredAction->hover(false);
+ d->hoveredAction = NULL;
+ update();
+ } else if (action != NULL) {
+ d->hoveredAction = action;
+ action->hover(true);
+ update();
+ }
+}
+
+void THBlackBar::mousePressEvent (QMouseEvent *event) {
+ QWidget::mousePressEvent(event);
+
+ if (d->hoveredAction != NULL) {
+
+ if (d->checkedAction != NULL) {
+ // already checked
+ if (d->checkedAction == d->hoveredAction) return;
+ d->checkedAction->setChecked(false);
+ }
+
+ d->checkedAction = d->hoveredAction;
+ d->hoveredAction->setChecked(true);
+ d->hoveredAction->trigger();
+
+ update();
+ }
+}
+
+THAction *THBlackBar::hoveredAction (const QPoint& pos) const {
+ if (pos.y() <= 0 || pos.y() >= height())
+ return(NULL);
+
+ /*
+ int buttonWidth = calculateButtonWidth();
+ int buttonsWidth = buttonWidth * d->actionList.size();
+ int buttonsX = (width() / 2) - (buttonsWidth / 2);
+ */
+
+ int buttonWidth = width() / d->actionList.size(); // calculateButtonWidth();
+ int buttonsWidth = width(); // buttonWidth * d->actionList.size();
+ int buttonsX = 0; // (width / 2) - (buttonsWidth / 2);
+
+ if (pos.x() <= buttonsX || pos.x() >= (buttonsX + buttonsWidth))
+ return(NULL);
+
+ int buttonIndex = (pos.x() - buttonsX) / buttonWidth;
+
+ if (buttonIndex >= d->actionList.size())
+ return(NULL);
+ return(d->actionList[buttonIndex]);
+}
+
+int THBlackBar::calculateButtonWidth (void) const {
+ QFont smallerBoldFont;
+ smallerBoldFont.setBold(true);
+ smallerBoldFont.setPointSize(smallerBoldFont.pointSize()*.85);
+ QFontMetrics fontMetrics(smallerBoldFont);
+ int tmpItemWidth, itemWidth = 0;
+ foreach (THAction *action, d->actionList) {
+ tmpItemWidth = fontMetrics.width(action->text());
+ if (itemWidth < tmpItemWidth) itemWidth = tmpItemWidth;
+ }
+ return itemWidth;
+}
+
+
+/* ============================================================================
+ * PRIVATE Methods
+ */
+void THBlackBar::drawUnselectedButton ( QPainter *painter,
+ const QRect& rect,
+ const THAction *action)
+{
+ QLinearGradient linearGrad(QPointF(0, 0), QPointF(0, rect.height() / 2));
+ linearGrad.setColorAt(0, QColor(0x8e, 0x8e, 0x8e));
+ linearGrad.setColorAt(1, QColor(0x5c, 0x5c, 0x5c));
+ /*
+ QPalette palette;
+ linearGrad.setColorAt(0, palette.color(QPalette::Dark));
+ linearGrad.setColorAt(1, palette.color(QPalette::Midlight));
+*/
+ drawButton(painter, rect, linearGrad, QColor(0x41, 0x41, 0x41), action);
+ // drawButton(painter, rect, linearGrad, palette.color(QPalette::Shadow), action);
+}
+
+void THBlackBar::drawSelectedButton ( QPainter *painter,
+ const QRect& rect,
+ const THAction *action)
+{
+ QLinearGradient linearGrad(QPointF(0, 0), QPointF(0, rect.height() / 2));
+ linearGrad.setColorAt(0, QColor(0x6d, 0x6d, 0x6d));
+ linearGrad.setColorAt(1, QColor(0x25, 0x25, 0x25));
+ drawButton(painter, rect, linearGrad, QColor(0x00, 0x00, 0x00), action);
+}
+
+void THBlackBar::drawButton ( QPainter *painter,
+ const QRect& rect,
+ const THAction *action)
+{
+ if (action->isChecked())
+ drawSelectedButton(painter, rect, action);
+ else
+ drawUnselectedButton(painter, rect, action);
+}
+
+void THBlackBar::drawButton ( QPainter *painter,
+ const QRect& rect,
+ const QLinearGradient& gradient,
+ const QColor& color,
+ const THAction *action)
+{
+ painter->save();
+
+ int height = rect.height();
+ int width = rect.width();
+ int mh = (height / 2);
+
+ painter->translate(rect.x(), rect.y());
+ painter->setPen(QColor(0x28, 0x28, 0x28));
+
+ painter->fillRect(0, 0, width, mh, QBrush(gradient));
+ painter->fillRect(0, mh, width, mh, color);
+ painter->drawRect(0, 0, width, height);
+
+ QFont smallerBoldFont;
+ smallerBoldFont.setBold(true);
+ smallerBoldFont.setPointSize(smallerBoldFont.pointSize()*.85);
+ painter->setFont(smallerBoldFont);
+ painter->setPen(QPen(QColor(0xff, 0xff, 0xff), 1));
+ painter->drawText(0, 1, width, height, Qt::AlignCenter, action->text());
+
+ painter->restore();
+}
+
--- /dev/null
+#ifndef _THBLACKBAR_H_
+#define _THBLACKBAR_H_
+
+#include <QWidget>
+class THAction;
+
+class THBlackBar : public QWidget {
+ Q_OBJECT
+
+ public:
+ THBlackBar (QWidget *parent = 0);
+ ~THBlackBar();
+
+ public:
+ THAction *addAction (THAction *action);
+ THAction *addAction (const QString& text);
+ void setCheckedAction(int index);
+
+ QSize minimumSizeHint (void) const;
+
+ protected:
+ void paintEvent (QPaintEvent *event);
+
+ void mouseMoveEvent (QMouseEvent *event);
+ void mousePressEvent (QMouseEvent *event);
+
+ private:
+ void drawUnselectedButton ( QPainter *painter,
+ const QRect& rect,
+ const THAction *action);
+ void drawSelectedButton ( QPainter *painter,
+ const QRect& rect,
+ const THAction *action);
+ void drawButton ( QPainter *painter,
+ const QRect& rect,
+ const THAction *action);
+ void drawButton ( QPainter *painter,
+ const QRect& rect,
+ const QLinearGradient& gradient,
+ const QColor& color,
+ const THAction *action);
+
+ THAction *hoveredAction (const QPoint& pos) const;
+ int calculateButtonWidth (void) const;
+
+ private:
+ class Private;
+ Private *d;
+};
+
+#endif /* !_THBLACKBAR_H_ */
+
--- /dev/null
+#include <QPaintEvent>
+
+#include "thblackbutton.h"
+#include "thpainter.h"
+
+/* ============================================================================
+ * PRIVATE Class
+ */
+class THBlackButton::Private {
+ public:
+ qreal leftTopRadius;
+ qreal leftBottomRadius;
+ qreal rightTopRadius;
+ qreal rightBottomRadius;
+};
+
+/* ============================================================================
+ * PUBLIC Constructor/Destructors
+ */
+THBlackButton::THBlackButton (QWidget *parent)
+ : QAbstractButton(parent), d(new THBlackButton::Private)
+{
+ setRadius(10);
+}
+
+THBlackButton::THBlackButton (const QString& text, QWidget *parent)
+ : QAbstractButton(parent), d(new THBlackButton::Private)
+{
+ setRadius(10);
+ setText(text);
+}
+
+THBlackButton::~THBlackButton() {
+ delete d;
+}
+
+/* ============================================================================
+ * PUBLIC Methods
+ */
+void THBlackButton::setRadius (qreal radius) {
+ d->leftTopRadius = radius;
+ d->leftBottomRadius = radius;
+ d->rightTopRadius = radius;
+ d->rightBottomRadius = radius;
+}
+
+void THBlackButton::setRadius ( qreal leftTopRadius,
+ qreal leftBottomRadius,
+ qreal rightTopRadius,
+ qreal rightBottomRadius)
+{
+ d->leftTopRadius = leftTopRadius;
+ d->leftBottomRadius = leftBottomRadius;
+ d->rightTopRadius = rightTopRadius;
+ d->rightBottomRadius = rightBottomRadius;
+}
+
+QSize THBlackButton::minimumSizeHint (void) const {
+ QFontMetrics fontMetrics(QFont("Arial", 8, QFont::Bold));
+ int width = fontMetrics.width(text()) + 48;
+ return(QSize(width, 22));
+}
+
+/* ============================================================================
+ * PROTECTED Methods
+ */
+void THBlackButton::paintEvent (QPaintEvent *event) {
+ int height = event->rect().height();
+ int width = event->rect().width() - 10;
+ int mh = (height / 2);
+
+ THPainter p(this);
+ p.setPen(QPen(QColor(0x28, 0x28, 0x28), 1));
+
+ p.translate(5, 0);
+
+ QLinearGradient linearGrad;
+ QColor color;
+ if (isDown()) {
+ linearGrad = QLinearGradient(QPointF(0, 0), QPointF(0, mh));
+ linearGrad.setColorAt(0, QColor(0x6c, 0x6c, 0x6c));
+ linearGrad.setColorAt(1, QColor(0x40, 0x40, 0x40));
+ color = QColor(0x35, 0x35, 0x35);
+ } else {
+ linearGrad = QLinearGradient(QPointF(0, 0), QPointF(0, mh));
+ linearGrad.setColorAt(0, QColor(0x8e, 0x8e, 0x8e));
+ linearGrad.setColorAt(1, QColor(0x5c, 0x5c, 0x5c));
+ color = QColor(0x41, 0x41, 0x41);
+ }
+
+ p.fillRoundRect(QRect(0, 0, width, mh),
+ d->leftTopRadius, 0, d->rightTopRadius, 0,
+ QBrush(linearGrad));
+ p.fillRoundRect(QRect(0, mh, width, mh),
+ 0, d->leftBottomRadius, 0, d->rightBottomRadius,
+ color);
+ p.drawRoundRect(QRect(0, 0, width, height),
+ d->leftTopRadius, d->leftBottomRadius,
+ d->rightTopRadius, d->rightBottomRadius);
+
+ p.translate(-5, 0);
+
+ p.setFont(QFont("Arial", 8, QFont::Bold));
+ p.setPen(QPen(QColor(0xff, 0xff, 0xff), 1));
+ p.drawText(event->rect(), Qt::AlignCenter, text());
+
+ p.end();
+}
+
--- /dev/null
+#ifndef _THBLACKBUTTON_H_
+#define _THBLACKBUTTON_H_
+
+#include <QAbstractButton>
+
+class THBlackButton : public QAbstractButton {
+ Q_OBJECT
+
+ public:
+ THBlackButton (QWidget *parent = 0);
+ THBlackButton (const QString& text, QWidget *parent = 0);
+ ~THBlackButton();
+
+ public:
+ void setRadius (qreal radius);
+ void setRadius (qreal leftTopRadius,
+ qreal leftBottomRadius,
+ qreal rightTopRadius,
+ qreal rightBottomRadius);
+
+ QSize minimumSizeHint (void) const;
+
+ protected:
+ void paintEvent (QPaintEvent *event);
+
+ private:
+ class Private;
+ Private *d;
+};
+
+#endif /* !_THBLACKBUTTON_H_ */
+
--- /dev/null
+#include "imageblur.h"
+#include "thpainter.h"
+#include "thimage.h"
+
+/* ============================================================================
+ * PUBLIC Constructor/Destructors
+ */
+THImage::THImage (const QSize& size, Format format)
+ : QImage(size, format)
+{
+}
+
+THImage::THImage (int width, int height, Format format)
+ : QImage(width, height, format)
+{
+}
+
+THImage::THImage (uchar *data, int width, int height, Format format)
+ : QImage(data, width, height, format)
+{
+}
+
+THImage::THImage (const uchar *data, int width, int height, Format format)
+ : QImage(data, width, height, format)
+{
+}
+
+THImage::THImage (uchar *data, int width, int height, int bytesPerLine, Format format)
+ : QImage(data, width, height, bytesPerLine, format)
+{
+}
+
+THImage::THImage (const uchar *data, int width, int height, int bytesPerLine, Format format)
+ : QImage(data, width, height, bytesPerLine, format)
+{
+}
+
+THImage::THImage (const QString& fileName, const char *format)
+ : QImage(fileName, format)
+{
+}
+
+THImage::THImage (const char *fileName, const char *format)
+ : QImage(fileName, format)
+{
+}
+
+THImage::THImage (const QImage& image)
+ : QImage(image)
+{
+}
+
+THImage::~THImage() {
+}
+
+/* ============================================================================
+ * PUBLIC Methods
+ */
+void THImage::expblur(int aprec, int zprec, int radius) {
+ ImageBlur::expblur(this, aprec, zprec, radius);
+}
+#include <QCoreApplication>
+
+void THImage::shadowBlur (int radius, const QColor& color) {
+ ImageBlur::expblur(this, 16, 7, radius);
+
+ THPainter p(this);
+ p.setCompositionMode(QPainter::CompositionMode_SourceIn);
+ p.fillRect(rect(), color);
+ p.end();
+}
+
+
--- /dev/null
+#ifndef _THIMAGE_H_
+#define _THIMAGE_H_
+
+#include <QImage>
+
+class THImage : public QImage {
+ public:
+ THImage (const QSize& size, Format format);
+ THImage (int width, int height, Format format);
+ THImage (uchar *data, int width, int height, Format format);
+ THImage (const uchar *data, int width, int height, Format format);
+ THImage (uchar *data, int width, int height, int bytesPerLine, Format format);
+ THImage (const uchar *data, int width, int height, int bytesPerLine, Format format);
+ THImage (const QString& fileName, const char *format = 0);
+ THImage (const char *fileName, const char *format = 0);
+ THImage (const QImage& image);
+ ~THImage();
+
+ public:
+ void expblur(int aprec, int zprec, int radius);
+ void shadowBlur (int radius, const QColor& color);
+};
+
+#endif /* !_THIMAGE_H_ */
+
+
--- /dev/null
+DEPENDPATH += $$PWD
+INCLUDEPATH += $$PWD
+
+HEADERS += thimage.h \
+ thaction.h \
+ thactiongroup.h \
+ thblackbar.h \
+ thblackbutton.h \
+ thpainter.h \
+ imageblur.h
+SOURCES += thimage.cpp \
+ thaction.cpp \
+ thactiongroup.cpp \
+ thblackbar.cpp \
+ thblackbutton.cpp \
+ thpainter.cpp \
+ imageblur.cpp
--- /dev/null
+#include "thpainter.h"
+#include "thimage.h"
+
+/* ============================================================================
+ * PUBLIC Constructor/Destructors
+ */
+THPainter::THPainter()
+ : QPainter()
+{
+ setRenderHint(QPainter::Antialiasing);
+ setRenderHint(QPainter::TextAntialiasing);
+}
+
+THPainter::THPainter(QPaintDevice *device)
+ : QPainter(device)
+{
+ setRenderHint(QPainter::Antialiasing);
+ setRenderHint(QPainter::TextAntialiasing);
+}
+
+THPainter::~THPainter() {
+}
+
+/* ============================================================================
+ * PUBLIC STATIC Methods
+ */
+QPainterPath THPainter::roundRectangle (const QRectF& rect, qreal radius) {
+ return(roundRectangle(rect, radius, radius, radius, radius));
+}
+
+QPainterPath THPainter::roundRectangle (const QRectF& rect,
+ qreal leftRadius,
+ qreal rightRadius)
+{
+ return(roundRectangle(rect, leftRadius, leftRadius, rightRadius, rightRadius));
+}
+
+QPainterPath THPainter::roundRectangle (const QRectF& rect,
+ qreal leftTopRadius,
+ qreal leftBottomRadius,
+ qreal rightTopRadius,
+ qreal rightBottomRadius)
+{
+ // Top Left Corner
+ // Top Side
+ // Top right corner
+ // Right side
+ // Bottom right corner
+ // Bottom side
+ // Bottom left corner
+
+ QPainterPath path(QPoint(rect.left(), rect.top() + leftTopRadius));
+ path.quadTo(rect.left(), rect.top(), rect.left() + leftTopRadius, rect.top());
+ path.lineTo(rect.right() - rightTopRadius, rect.top());
+ path.quadTo(rect.right(), rect.top(), rect.right(), rect.top() + rightTopRadius);
+ path.lineTo(rect.right(), rect.bottom() - rightBottomRadius);
+ path.quadTo(rect.right(), rect.bottom(), rect.right() - rightBottomRadius, rect.bottom());
+ path.lineTo(rect.left() + leftBottomRadius, rect.bottom());
+ path.quadTo(rect.left(), rect.bottom(), rect.left(), rect.bottom() - leftBottomRadius);
+ path.closeSubpath();
+
+ return(path);
+}
+
+/* ============================================================================
+ * PUBLIC Draw Methods
+ */
+void THPainter::drawRoundRect (const QRectF& rect, qreal radius) {
+ drawPath(roundRectangle(rect, radius, radius, radius, radius));
+}
+
+void THPainter::drawRoundRect (const QRectF& rect,
+ qreal leftRadius,
+ qreal rightRadius)
+{
+ drawPath(roundRectangle(rect, leftRadius, leftRadius,
+ rightRadius, rightRadius));
+}
+
+void THPainter::drawRoundRect ( const QRectF& rect,
+ qreal leftTopRadius,
+ qreal leftBottomRadius,
+ qreal rightTopRadius,
+ qreal rightBottomRadius)
+{
+ drawPath(roundRectangle(rect, leftTopRadius, leftBottomRadius,
+ rightTopRadius, rightBottomRadius));
+}
+
+void THPainter::drawShadowText (qreal x, qreal y,
+ const QString& text,
+ const QColor& shadowColor,
+ const QPointF& offset,
+ qreal radius)
+{
+ QPainter p;
+
+ // Draw Text
+ QRect textRect = QFontMetrics(text).boundingRect(text);
+ QImage textImage(textRect.size(), QImage::Format_ARGB32_Premultiplied);
+ textImage.fill(Qt::transparent);
+ p.begin(&textImage);
+ p.setPen(pen());
+ p.setFont(font());
+ p.drawText(textImage.rect(), Qt::AlignLeft, text);
+ p.end();
+
+ // Draw Blurred Shadow
+ THImage shadowImage(textRect.size() + QSize(radius * 2, radius * 2),
+ QImage::Format_ARGB32_Premultiplied);
+ shadowImage.fill(Qt::transparent);
+ p.begin(&shadowImage);
+ p.drawImage(radius, radius, textImage);
+ p.end();
+ shadowImage.shadowBlur(radius, shadowColor);
+
+ // Compose Text and Shadow
+ int addSizeX = (offset.x() > radius) ? (abs(offset.x()) - radius) : 0;
+ int addSizeY = (offset.y() > radius) ? (abs(offset.y()) - radius) : 0;
+ QSize finalSize = shadowImage.size() + QSize(addSizeX, addSizeY);
+
+ QPointF shadowTopLeft(QPointF((finalSize.width() - shadowImage.width()) / 2.0,
+ (finalSize.height() - shadowImage.height()) / 2.0) +
+ (offset / 2.0));
+ QPointF textTopLeft(QPointF((finalSize.width() - textImage.width()) / 2.0,
+ ((finalSize.height() - textImage.height()) / 2.0)) -
+ (offset / 2.0));
+
+ // Paint Text and Shadow
+ save();
+ translate(x, y);
+ setCompositionMode(QPainter::CompositionMode_Xor);
+ drawImage(shadowTopLeft, shadowImage);
+ drawImage(textTopLeft, textImage);
+ restore();
+}
+
+/* ============================================================================
+ * PUBLIC Fill Methods
+ */
+void THPainter::fillRoundRect ( const QRectF& rect,
+ qreal radius,
+ const QBrush& brush)
+{
+ fillPath(roundRectangle(rect, radius, radius, radius, radius), brush);
+}
+
+void THPainter::fillRoundRect ( const QRectF& rect,
+ qreal leftRadius,
+ qreal rightRadius,
+ const QBrush& brush)
+{
+ fillPath(roundRectangle(rect, leftRadius, leftRadius,
+ rightRadius, rightRadius), brush);
+}
+
+void THPainter::fillRoundRect ( const QRectF& rect,
+ qreal leftTopRadius,
+ qreal leftBottomRadius,
+ qreal rightTopRadius,
+ qreal rightBottomRadius,
+ const QBrush& brush)
+{
+ fillPath(roundRectangle(rect, leftTopRadius, leftBottomRadius,
+ rightTopRadius, rightBottomRadius), brush);
+}
+
--- /dev/null
+#ifndef _THPAINTER_H_
+#define _THPAINTER_H_
+
+#include <QPainter>
+
+class THPainter : public QPainter {
+ public:
+ THPainter();
+ THPainter(QPaintDevice *device);
+ ~THPainter();
+
+ // STATIC Methods
+ static QPainterPath roundRectangle (const QRectF& rect, qreal radius);
+ static QPainterPath roundRectangle (const QRectF& rect,
+ qreal leftRadius,
+ qreal rightRadius);
+ static QPainterPath roundRectangle (const QRectF& rect,
+ qreal leftTopRadius,
+ qreal leftBottomRadius,
+ qreal rightTopRadius,
+ qreal rightBottomRadius);
+
+ // Methods
+ void drawShadowText ( qreal x, qreal y,
+ const QString& text,
+ const QColor& shadowColor,
+ const QPointF& offset,
+ qreal radius);
+
+ void drawRoundRect (const QRectF& rect,
+ qreal radius);
+ void drawRoundRect (const QRectF& rect,
+ qreal leftRadius,
+ qreal rightRadius);
+ void drawRoundRect (const QRectF& rect,
+ qreal leftTopRadius,
+ qreal leftBottomRadius,
+ qreal rightTopRadius,
+ qreal rightBottomRadius);
+
+ void fillRoundRect (const QRectF& rect,
+ qreal radius,
+ const QBrush& brush);
+ void fillRoundRect (const QRectF& rect,
+ qreal leftRadius,
+ qreal rightRadius,
+ const QBrush& brush);
+ void fillRoundRect (const QRectF& rect,
+ qreal leftTopRadius,
+ qreal leftBottomRadius,
+ qreal rightTopRadius,
+ qreal rightBottomRadius,
+ const QBrush& brush);
+};
+
+#endif /* !_THPAINTER_H_ */
+
--- /dev/null
+#include "updatechecker.h"
+#include "networkaccess.h"
+#include "Constants.h"
+
+namespace The {
+ NetworkAccess* http();
+}
+
+UpdateChecker::UpdateChecker() {
+ m_needUpdate = false;
+}
+
+void UpdateChecker::checkForUpdate() {
+ QUrl updateUrl(QString(Constants::WEBSITE) + "-ws/release.xml");
+ // QUrl updateUrl("http://flavio.tordini.org:8012/release.xml");
+
+ QObject *reply = The::http()->get(updateUrl);
+ connect(reply, SIGNAL(data(QByteArray)), SLOT(requestFinished(QByteArray)));
+
+}
+
+void UpdateChecker::requestFinished(QByteArray data) {
+ UpdateCheckerStreamReader reader;
+ reader.read(data);
+ m_needUpdate = reader.needUpdate();
+ m_remoteVersion = reader.remoteVersion();
+ if (m_needUpdate) emit newVersion(m_remoteVersion);
+}
+
+QString UpdateChecker::remoteVersion() {
+ return m_remoteVersion;
+}
+
+// --- Reader ---
+
+bool UpdateCheckerStreamReader::read(QByteArray data) {
+ addData(data);
+
+ while (!atEnd()) {
+ readNext();
+ if (isStartElement()) {
+ if (name() == "release") {
+ while (!atEnd()) {
+ readNext();
+ if (isStartElement() && name() == "version") {
+ QString remoteVersion = readElementText();
+ qDebug() << remoteVersion << QString(Constants::VERSION);
+ m_needUpdate = remoteVersion != QString(Constants::VERSION);
+ m_remoteVersion = remoteVersion;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return !error();
+}
+
+QString UpdateCheckerStreamReader::remoteVersion() {
+ return m_remoteVersion;
+}
--- /dev/null
+#ifndef UPDATECHECKER_H
+#define UPDATECHECKER_H
+
+#include <QXmlStreamReader>
+#include <QNetworkReply>
+
+class UpdateChecker : public QObject {
+ Q_OBJECT
+
+public:
+ UpdateChecker();
+ void checkForUpdate();
+ QString remoteVersion();
+
+signals:
+ void newVersion(QString);
+
+private slots:
+ void requestFinished(QByteArray);
+
+private:
+
+ bool m_needUpdate;
+ QString m_remoteVersion;
+ QNetworkReply *networkReply;
+
+};
+
+class UpdateCheckerStreamReader : public QXmlStreamReader {
+
+public:
+ bool read(QByteArray data);
+ QString remoteVersion();
+ bool needUpdate() { return m_needUpdate; }
+
+private:
+ QString m_remoteVersion;
+ bool m_needUpdate;
+
+};
+
+#endif // UPDATECHECKER_H
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License versions 2.0 or 3.0 as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information
+** to ensure GNU General Public Licensing requirements will be met:
+** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
+** http://www.gnu.org/copyleft/gpl.html. In addition, as a special
+** exception, Nokia gives you certain additional rights. These rights
+** are described in the Nokia Qt GPL Exception version 1.3, included in
+** the file GPL_EXCEPTION.txt in this package.
+**
+** Qt for Windows(R) Licensees
+** As a special exception, Nokia, as the sole copyright holder for Qt
+** Designer, grants users of the Qt/Eclipse Integration plug-in the
+** right for the Qt/Eclipse Integration to link to functionality
+** provided by Qt Designer and its related libraries.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+#include "urllineedit.h"
+#include "searchlineedit.h"
+
+#include <QtCore/QEvent>
+
+#include <QtGui/QApplication>
+#include <QtGui/QCompleter>
+#include <QtGui/QFocusEvent>
+#include <QtGui/QHBoxLayout>
+#include <QtGui/QLabel>
+#include <QtGui/QLineEdit>
+#include <QtGui/QPainter>
+#include <QtGui/QStyle>
+#include <QtGui/QStyleOptionFrameV2>
+
+#include <QtCore/QDebug>
+
+ExLineEdit::ExLineEdit(QWidget *parent)
+ : QWidget(parent)
+ , m_leftWidget(0)
+ , m_lineEdit(new QLineEdit(this))
+ , m_clearButton(0)
+{
+ setFocusPolicy(m_lineEdit->focusPolicy());
+ setAttribute(Qt::WA_InputMethodEnabled);
+ setSizePolicy(m_lineEdit->sizePolicy());
+ setBackgroundRole(m_lineEdit->backgroundRole());
+ setMouseTracking(true);
+ setAcceptDrops(true);
+ setAttribute(Qt::WA_MacShowFocusRect, true);
+ QPalette p = m_lineEdit->palette();
+ setPalette(p);
+
+ // line edit
+ m_lineEdit->setFrame(false);
+ m_lineEdit->setFocusProxy(this);
+ m_lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false);
+ QPalette clearPalette = m_lineEdit->palette();
+ clearPalette.setBrush(QPalette::Base, QBrush(Qt::transparent));
+ m_lineEdit->setPalette(clearPalette);
+
+ // clearButton
+ m_clearButton = new ClearButton(this);
+ connect(m_clearButton, SIGNAL(clicked()),
+ m_lineEdit, SLOT(clear()));
+ connect(m_lineEdit, SIGNAL(textChanged(const QString&)),
+ m_clearButton, SLOT(textChanged(const QString&)));
+}
+
+void ExLineEdit::setFont(const QFont &font) {
+ m_lineEdit->setFont(font);
+ updateGeometries();
+}
+
+void ExLineEdit::setLeftWidget(QWidget *widget)
+{
+ m_leftWidget = widget;
+}
+
+QWidget *ExLineEdit::leftWidget() const
+{
+ return m_leftWidget;
+}
+
+void ExLineEdit::resizeEvent(QResizeEvent *event)
+{
+ Q_ASSERT(m_leftWidget);
+ updateGeometries();
+ QWidget::resizeEvent(event);
+}
+
+void ExLineEdit::updateGeometries()
+{
+ QStyleOptionFrameV2 panel;
+ initStyleOption(&panel);
+ QRect rect = style()->subElementRect(QStyle::SE_LineEditContents, &panel, this);
+
+ int padding = 3;
+ // int height = rect.height() + padding*2;
+ int width = rect.width();
+
+ // int m_leftWidgetHeight = m_leftWidget->height();
+ m_leftWidget->setGeometry(rect.x() + 2, 0,
+ m_leftWidget->width(), m_leftWidget->height());
+
+ int clearButtonWidth = this->height();
+ m_lineEdit->setGeometry(m_leftWidget->x() + m_leftWidget->width(), padding,
+ width - clearButtonWidth - m_leftWidget->width(), this->height() - padding*2);
+
+ m_clearButton->setGeometry(this->width() - clearButtonWidth, 0,
+ clearButtonWidth, this->height());
+}
+
+void ExLineEdit::initStyleOption(QStyleOptionFrameV2 *option) const
+{
+ option->initFrom(this);
+ option->rect = contentsRect();
+ option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, option, this);
+ option->midLineWidth = 0;
+ option->state |= QStyle::State_Sunken;
+ if (m_lineEdit->isReadOnly())
+ option->state |= QStyle::State_ReadOnly;
+#ifdef QT_KEYPAD_NAVIGATION
+ if (hasEditFocus())
+ option->state |= QStyle::State_HasEditFocus;
+#endif
+ option->features = QStyleOptionFrameV2::None;
+}
+
+QSize ExLineEdit::sizeHint() const
+{
+ m_lineEdit->setFrame(true);
+ QSize size = m_lineEdit->sizeHint();
+ m_lineEdit->setFrame(false);
+ return size;
+}
+
+void ExLineEdit::focusInEvent(QFocusEvent *event)
+{
+ m_lineEdit->event(event);
+ QWidget::focusInEvent(event);
+}
+
+void ExLineEdit::focusOutEvent(QFocusEvent *event)
+{
+ m_lineEdit->event(event);
+
+ if (m_lineEdit->completer()) {
+ connect(m_lineEdit->completer(), SIGNAL(activated(QString)),
+ m_lineEdit, SLOT(setText(QString)));
+ connect(m_lineEdit->completer(), SIGNAL(highlighted(QString)),
+ m_lineEdit, SLOT(_q_completionHighlighted(QString)));
+ }
+ QWidget::focusOutEvent(event);
+}
+
+void ExLineEdit::keyPressEvent(QKeyEvent *event)
+{
+ m_lineEdit->event(event);
+ QWidget::keyPressEvent(event);
+}
+
+bool ExLineEdit::event(QEvent *event)
+{
+ if (event->type() == QEvent::ShortcutOverride)
+ m_lineEdit->event(event);
+ return QWidget::event(event);
+}
+
+void ExLineEdit::paintEvent(QPaintEvent *)
+{
+ QPainter p(this);
+ QStyleOptionFrameV2 panel;
+ initStyleOption(&panel);
+ style()->drawPrimitive(QStyle::PE_PanelLineEdit, &panel, &p, this);
+}
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the demonstration applications of the Qt Toolkit.
+**
+** Commercial Usage
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License versions 2.0 or 3.0 as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information
+** to ensure GNU General Public Licensing requirements will be met:
+** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
+** http://www.gnu.org/copyleft/gpl.html. In addition, as a special
+** exception, Nokia gives you certain additional rights. These rights
+** are described in the Nokia Qt GPL Exception version 1.3, included in
+** the file GPL_EXCEPTION.txt in this package.
+**
+** Qt for Windows(R) Licensees
+** As a special exception, Nokia, as the sole copyright holder for Qt
+** Designer, grants users of the Qt/Eclipse Integration plug-in the
+** right for the Qt/Eclipse Integration to link to functionality
+** provided by Qt Designer and its related libraries.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+**
+****************************************************************************/
+
+#ifndef URLLINEEDIT_H
+#define URLLINEEDIT_H
+
+#include <QtCore/QUrl>
+#include <QtGui/QWidget>
+#include <QtGui/QLineEdit>
+#include <QtGui/QStyleOptionFrame>
+
+QT_BEGIN_NAMESPACE
+class QLineEdit;
+QT_END_NAMESPACE
+
+class ClearButton;
+class ExLineEdit : public QWidget
+{
+ Q_OBJECT
+
+public:
+ ExLineEdit(QWidget *parent = 0);
+
+ inline QLineEdit *lineEdit() const { return m_lineEdit; }
+
+ void setLeftWidget(QWidget *widget);
+ QWidget *leftWidget() const;
+ void clear() {
+ m_lineEdit->clear();
+ }
+ QString text() {
+ return m_lineEdit->text();
+ }
+ QSize sizeHint() const;
+ void updateGeometries();
+ void setFont(const QFont &);
+
+protected:
+ void focusInEvent(QFocusEvent *event);
+ void focusOutEvent(QFocusEvent *event);
+ void keyPressEvent(QKeyEvent *event);
+ void paintEvent(QPaintEvent *event);
+ void resizeEvent(QResizeEvent *event);
+ bool event(QEvent *event);
+ void initStyleOption(QStyleOptionFrameV2 *option) const;
+
+ QWidget *m_leftWidget;
+ QLineEdit *m_lineEdit;
+ ClearButton *m_clearButton;
+};
+
+#endif // URLLINEEDIT_H
+
--- /dev/null
+#include "video.h"
+#include "networkaccess.h"
+#include <QtNetwork>
+
+namespace The {
+ NetworkAccess* http();
+}
+
+Video::Video() : m_thumbnailUrls(QList<QUrl>()) {
+ m_duration = 0;
+ m_viewCount = -1;
+}
+
+void Video::preloadThumbnail() {
+ if (m_thumbnailUrls.isEmpty()) return;
+ QObject *reply = The::http()->get(m_thumbnailUrls.first());
+ connect(reply, SIGNAL(data(QByteArray)), SLOT(setThumbnail(QByteArray)));
+}
+
+void Video::setThumbnail(QByteArray bytes) {
+ m_thumbnail = QImage::fromData(bytes);
+ emit gotThumbnail();
+}
+
+const QImage Video::thumbnail() const {
+ return m_thumbnail;
+}
+
+bool Video::getVideoUrl() {
+
+ // https://develop.participatoryculture.org/trac/democracy/browser/trunk/tv/portable/flashscraper.py
+
+ QUrl webpage = m_webpage;\
+ // if (webpage == ) return false;
+ // qDebug() << webpage.toString();
+
+ // Get Video ID
+ // youtube-dl line 428
+ // QRegExp re("^((?:http://)?(?:\\w+\\.)?youtube\\.com/(?:(?:v/)|(?:(?:watch(?:\\.php)?)?\\?(?:.+&)?v=)))?([0-9A-Za-z_-]+)(?(1).+)?$");
+ QRegExp re("^http://www\\.youtube\\.com/watch\\?v=([0-9A-Za-z_-]+)$");
+ bool match = re.exactMatch(webpage.toString());
+ if (!match || re.numCaptures() < 1) return false;
+ QString videoId = re.cap(1);
+ // if (!videoId) return false;
+ // qDebug() << videoId;
+
+ // Get Video Token
+ QUrl normalizedUrl = QUrl(QString("http://www.youtube.com/get_video_info?video_id=")
+ .append(videoId).append("&el=embedded&ps=default&eurl="));
+
+ /*
+ QObject *reply = The::http()->get(normalizedUrl);
+ connect(reply, SIGNAL(data(QByteArray)), SLOT(gotVideoInfo(QByteArray)));
+ return true;
+ */
+
+ QString videoInfo = The::http()->syncGetString(normalizedUrl);
+ // qDebug() << videoInfo;
+ re = QRegExp("^.*&token=([^&]+).*$");
+ match = re.exactMatch(videoInfo);
+ if (!match || re.numCaptures() < 1) return false;
+ QString videoToken = re.cap(1);
+ // FIXME proper decode
+ videoToken = videoToken.replace("%3D", "=");
+ // qDebug() << "token" << videoToken;
+
+ m_streamUrl = QUrl(QString("http://www.youtube.com/get_video?video_id=")
+ .append(videoId)
+ .append("&t=").append(videoToken)
+ .append("&eurl=&el=embedded&ps=default&fmt=18"));
+
+ // qDebug() << videoUrl;
+
+ // follow redirects
+ /*
+ while (true) {
+ QHttpResponseHeader headers = syncHttp->head(videoUrl);
+ qDebug() << headers.values();
+ if (headers.hasKey("Location")) {
+ videoUrl = QUrl(headers.value("Location"), QUrl::StrictMode);
+ // qDebug() << videoUrl;
+ } else break;
+ }*/
+
+ return true;
+}
+
+
+/*
+void Video::gotVideoInfo(QByteArray data) {
+ QString videoInfo = QString::fromUtf8(data);
+ // qDebug() << videoInfo;
+ QRegExp re = QRegExp("^.*&token=([^&]+).*$");
+ bool match = re.exactMatch(videoInfo);
+ if (!match || re.numCaptures() < 1) return;
+ QString videoToken = re.cap(1);
+ // FIXME proper decode
+ videoToken = videoToken.replace("%3D", "=");
+ // qDebug() << "token" << videoToken;
+
+ QUrl videoUrl = QUrl(QString("http://www.youtube.com/get_video?video_id=")
+ .append(videoId)
+ .append("&t=").append(videoToken)
+ .append("&eurl=&el=embedded&ps=default&fmt=18"));
+
+ m_streamUrl = videoUrl;
+}
+*/
--- /dev/null
+#ifndef VIDEO_H
+#define VIDEO_H
+
+#include <QtGui>
+#include <QtNetwork>
+
+class Video : public QObject {
+
+ Q_OBJECT
+
+public:
+ Video();
+
+ const QString title() const { return m_title; }
+ void setTitle( QString title ) { m_title = title; }
+
+ const QString description() const { return m_description; }
+ void setDescription( QString description ) { m_description = description; }
+
+ const QString author() const { return m_author; }
+ void setAuthor( QString author ) { m_author = author; }
+
+ const QUrl webpage() const { return m_webpage; }
+ void setWebpage( QUrl webpage ) { m_webpage = webpage; }
+
+ const QUrl streamUrl() {
+ if (m_streamUrl.isEmpty())
+ this->getVideoUrl();
+ return m_streamUrl;
+ }
+ void setStreamUrl( QUrl streamUrl ) { m_streamUrl = streamUrl; }
+
+ QList<QUrl> thumbnailUrls() const { return m_thumbnailUrls; }
+ void addThumbnailUrl(QUrl url) {
+ m_thumbnailUrls << url;
+ }
+
+ void preloadThumbnail();
+ const QImage thumbnail() const;
+
+ int duration() const { return m_duration; }
+ void setDuration( int duration ) { m_duration = duration; }
+
+ int viewCount() const { return m_viewCount; }
+ void setViewCount( int viewCount ) { m_viewCount = viewCount; }
+
+ const QDateTime published() const { return m_published; }
+ void setPublished( QDateTime published ) { m_published = published; }
+
+public slots:
+ void setThumbnail(QByteArray bytes);
+
+signals:
+ void gotThumbnail();
+
+private slots:
+ // void gotVideoInfo(QByteArray);
+
+private:
+ bool getVideoUrl();
+
+ QString m_title;
+ QString m_description;
+ QString m_author;
+ // QUrl m_authorUrl;
+ QUrl m_webpage;
+ QUrl m_streamUrl;
+ QImage m_thumbnail;
+ QList<QUrl> m_thumbnailUrls;
+ // QList<QImage> m_thumbnails;
+ int m_duration;
+ QDateTime m_published;
+ int m_viewCount;
+
+};
+
+// This is required in order to use QPointer<Video> as a QVariant
+// as used by the Model/View playlist
+typedef QPointer<Video> VideoPointer;
+Q_DECLARE_METATYPE(VideoPointer)
+
+#endif // VIDEO_H
--- /dev/null
+#include "videomimedata.h"
+
+VideoMimeData::VideoMimeData() {}
+
+QStringList VideoMimeData::formats() const {
+ QStringList formats( QMimeData::formats() );
+ formats.append("application/x-minitube-video");
+ return formats;
+}
+
+bool VideoMimeData::hasFormat( const QString &mimeType ) const {
+ return mimeType == "application/x-minitube-video";
+}
--- /dev/null
+#ifndef VIDEOMIMEDATA_H
+#define VIDEOMIMEDATA_H
+
+#include <QMimeData>
+#include "video.h"
+
+class VideoMimeData : public QMimeData {
+
+public:
+ VideoMimeData();
+
+ virtual QStringList formats() const;
+ virtual bool hasFormat( const QString &mimeType ) const;
+
+ QList<Video*> videos() const { return m_videos; }
+
+ void addVideo(Video *video) {
+ m_videos << video;
+ }
+
+private:
+ QList<Video*> m_videos;
+
+};
+
+#endif // VIDEOMIMEDATA_H
--- /dev/null
+#include "videowidget.h"
+
+VideoWidget::VideoWidget(QWidget *parent) : Phonon::VideoWidget(parent) {
+
+}
+
+void VideoWidget::mouseDoubleClickEvent(QMouseEvent *event) {
+ switch(event->button()) {
+ case Qt::LeftButton:
+ emit doubleClicked();
+ break;
+ case Qt::RightButton:
+
+ break;
+ }
+}
+
+void VideoWidget::mousePressEvent(QMouseEvent *event) {
+ switch(event->button()) {
+ case Qt::LeftButton:
+
+ break;
+ case Qt::RightButton:
+ emit rightClicked();
+ break;
+ }
+}
--- /dev/null
+#ifndef VIDEOWIDGET_H
+#define VIDEOWIDGET_H
+
+#include <QtGui>
+#include <phonon>
+
+class VideoWidget : public Phonon::VideoWidget {
+
+ Q_OBJECT
+
+public:
+ VideoWidget(QWidget *parent);
+
+signals:
+ void doubleClicked();
+ void rightClicked();
+
+protected:
+ void mouseDoubleClickEvent(QMouseEvent *event);
+ void mousePressEvent(QMouseEvent *event);
+
+};
+
+#endif // VIDEOWIDGET_H
--- /dev/null
+#include "youtubesearch.h"
+#include "youtubestreamreader.h"
+#include "Constants.h"
+#include "networkaccess.h"
+
+namespace The {
+ NetworkAccess* http();
+}
+
+YouTubeSearch::YouTubeSearch() : QObject() {}
+
+void YouTubeSearch::search(SearchParams *searchParams, int max, int skip) {
+ this->abortFlag = false;
+
+ QString urlString = QString(
+ "http://gdata.youtube.com/feeds/api/videos?q=%1&max-results=%2&start-index=%3")
+ .arg(searchParams->keywords(), QString::number(max), QString::number(skip));
+
+ switch (searchParams->sortBy()) {
+ case SearchParams::SortByNewest:
+ urlString.append("&orderby=published");
+ break;
+ case SearchParams::SortByViewCount:
+ urlString.append("&orderby=viewCount");
+ break;
+ }
+
+ // QString urlString = QString("http://localhost/oringo/video.xml?q=%1&max-results=%2&start-index=%3")
+ // .arg(query, QString::number(max), QString::number(skip));
+
+ QUrl url(urlString);
+
+ QObject *reply = The::http()->get(url);
+ // connect(reply, SIGNAL(ready(QNetworkReply*)), SLOT(searchReady(QNetworkReply*)));
+ connect(reply, SIGNAL(data(QByteArray)), SLOT(parseResults(QByteArray)));
+
+}
+
+void YouTubeSearch::parseResults(QByteArray data) {
+
+ YouTubeStreamReader reader;
+ if (!reader.read(data)) {
+ qDebug() << "Error parsing XML";
+ }
+ videos = reader.getVideos();
+
+ foreach (Video *video, videos) {
+ // send it to the model
+ emit gotVideo(video);
+ }
+
+ foreach (Video *video, videos) {
+ // preload the thumb
+ if (abortFlag) return;
+ video->preloadThumbnail();
+ }
+
+ emit finished(videos.size());
+}
+
+QList<Video*> YouTubeSearch::getResults() {
+ return videos;
+}
+
+void YouTubeSearch::abort() {
+ this->abortFlag = true;
+}
--- /dev/null
+#ifndef YOUTUBESEARCH_H
+#define YOUTUBESEARCH_H
+
+#include "video.h"
+#include "searchparams.h"
+
+class YouTubeSearch : public QObject {
+
+ Q_OBJECT
+
+public:
+ YouTubeSearch();
+ void search(SearchParams *searchParams, int max, int skip);
+ void abort();
+ QList<Video*> getResults();
+
+signals:
+ void gotVideo(Video*);
+ void finished(int total);
+
+private slots:
+ void parseResults(QByteArray data);
+
+private:
+
+ QList<Video*> videos;
+
+ bool abortFlag;
+
+};
+
+#endif // YOUTUBESEARCH_H
--- /dev/null
+#include "youtubestreamreader.h"
+#include <QtGui>
+
+
+YouTubeStreamReader::YouTubeStreamReader() {
+
+}
+
+bool YouTubeStreamReader::read(QByteArray data) {
+ addData(data);
+
+ while (!atEnd()) {
+ readNext();
+ if (isStartElement()) {
+ if (name() == "feed") {
+ while (!atEnd()) {
+ readNext();
+ if (isStartElement() && name() == "entry") {
+ readEntry();
+ }
+ }
+ }
+ }
+ }
+
+ return !error();
+}
+
+void YouTubeStreamReader::readMediaGroup() {
+
+}
+
+void YouTubeStreamReader::readEntry() {
+ Video* video = new Video();
+ // qDebug(" *** ENTRY ***");
+
+ while (!atEnd()) {
+ readNext();
+
+ /*
+ qDebug() << name();
+ QXmlStreamAttribute attribute;
+ foreach (attribute, attributes())
+ qDebug() << attribute.name() << ":" << attribute.value();
+ */
+
+ if (isEndElement() && name() == "entry") break;
+ if (isStartElement()) {
+
+ if (name() == "link"
+ && attributes().value("rel").toString() == "alternate"
+ && attributes().value("type").toString() == "text/html"
+ ) {
+ QString webpage = attributes().value("href").toString();
+ // qDebug() << "Webpage: " << webpage;
+ video->setWebpage(QUrl(webpage));
+ } else if (name() == "author") {
+ readNext();
+ if (name() == "name") {
+ QString author = readElementText();
+ // qDebug() << "Author: " << author;
+ video->setAuthor(author);
+ }
+ } else if (name() == "published") {
+ video->setPublished(QDateTime::fromString(readElementText(), Qt::ISODate));
+ } else if (namespaceUri() == "http://gdata.youtube.com/schemas/2007" && name() == "statistics") {
+
+ QString viewCount = attributes().value("viewCount").toString();
+ // qDebug() << "viewCount: " << viewCount;
+ video->setViewCount(viewCount.toInt());
+ }
+ else if (namespaceUri() == "http://search.yahoo.com/mrss/" && name() == "group") {
+
+ // read media group
+ while (!atEnd()) {
+ readNext();
+ if (isEndElement() && name() == "group") break;
+ if (isStartElement()) {
+ if (name() == "thumbnail") {
+ // qDebug() << "Thumb: " << attributes().value("url").toString();
+ // video->thumbnailUrls() << QUrl(attributes().value("url").toString());
+ video->addThumbnailUrl(QUrl(attributes().value("url").toString()));
+ }
+ else if (name() == "title") {
+ QString title = readElementText();
+ // qDebug() << "Title: " << title;
+ video->setTitle(title);
+ }
+ /*
+ else if (name() == "description") {
+ QString desc = readElementText();
+ qDebug() << "Description: " << desc;
+ video->setDescription(desc);
+ } */
+ else if (name() == "duration") {
+ QString duration = attributes().value("seconds").toString();
+ // qDebug() << "Duration: " << duration;
+ video->setDuration(duration.toInt());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ videos.append(video);
+
+}
+
+QList<Video*> YouTubeStreamReader::getVideos() {
+ return videos;
+}
--- /dev/null
+#ifndef YOUTUBESTREAMREADER_H
+#define YOUTUBESTREAMREADER_H
+
+#include <QXmlStreamReader>
+#include <QBuffer>
+#include "video.h"
+
+class YouTubeStreamReader : public QXmlStreamReader
+{
+public:
+ YouTubeStreamReader();
+ bool read(QByteArray data);
+ QList<Video*> getVideos();
+
+private:
+ void readMediaGroup();
+ void readEntry();
+ QList<Video*> videos;
+};
+
+#endif // YOUTUBESTREAMREADER_H
--- /dev/null
+<?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="32"
+ height="32"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docbase="/home/needcoffee/Templates"
+ sodipodi:docname="tv.svg"
+ version="1.0"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/home/flavio/projects/minitube/images/app.png"
+ inkscape:export-xdpi="360"
+ inkscape:export-ydpi="360">
+ <defs
+ id="defs4">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 16 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="32 : 16 : 1"
+ inkscape:persp3d-origin="16 : 10.666667 : 1"
+ id="perspective65" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient8228">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop8230" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop8232" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient7815">
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="0"
+ id="stop7817" />
+ <stop
+ id="stop7823"
+ offset="0.5"
+ style="stop-color:#000000;stop-opacity:1;" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop7819" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient7422">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop7424" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop7426" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient6646">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1"
+ offset="0"
+ id="stop6648" />
+ <stop
+ style="stop-color:#555753;stop-opacity:1"
+ offset="1"
+ id="stop6650" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient6257">
+ <stop
+ style="stop-color:#555753;stop-opacity:1;"
+ offset="0"
+ id="stop6259" />
+ <stop
+ style="stop-color:#2e3436;stop-opacity:1"
+ offset="1"
+ id="stop6261" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5863">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop5865" />
+ <stop
+ style="stop-color:#2e3436;stop-opacity:1"
+ offset="1"
+ id="stop5867" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5088">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.54347825;"
+ offset="0"
+ id="stop5090" />
+ <stop
+ style="stop-color:#729fcf;stop-opacity:1;"
+ offset="1"
+ id="stop5092" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4701"
+ inkscape:collect="always">
+ <stop
+ id="stop4703"
+ offset="0"
+ style="stop-color:#729fcf;stop-opacity:1" />
+ <stop
+ id="stop4705"
+ offset="1"
+ style="stop-color:#3465a4;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4301">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4303" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4305" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient3145">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3147" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3149" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3145"
+ id="linearGradient3151"
+ x1="7"
+ y1="-1"
+ x2="12"
+ y2="16"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.6666666,0,0,1.0000001,-2.7699557e-8,-3)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4301"
+ id="linearGradient4307"
+ x1="-5"
+ y1="-7"
+ x2="39"
+ y2="30"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.5555555,0,0,0.5692092,2.6666665,1.1694899)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4701"
+ id="radialGradient4699"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.0025642,3.1703514e-8,-1.117005e-8,0.4159999,-8.0615381,4.9319989)"
+ cx="24"
+ cy="39.923077"
+ fx="24"
+ fy="39.923077"
+ r="20" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5088"
+ id="linearGradient5097"
+ gradientUnits="userSpaceOnUse"
+ x1="2"
+ y1="-4"
+ x2="19"
+ y2="30"
+ gradientTransform="matrix(0.5675677,0,0,0.6086956,2.3783783,0.5000008)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5863"
+ id="linearGradient5872"
+ gradientUnits="userSpaceOnUse"
+ x1="8"
+ y1="37"
+ x2="8"
+ y2="41"
+ gradientTransform="matrix(3.1249998,0,0,0.9999976,-10.562498,-13.999915)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6257"
+ id="linearGradient6263"
+ x1="12.283331"
+ y1="41.250008"
+ x2="12.283331"
+ y2="36.750004"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.5405406,0,0,-0.3333337,1.5270266,38.000017)" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6646"
+ id="radialGradient6652"
+ cx="12"
+ cy="8.4459467"
+ fx="12"
+ fy="8.4459467"
+ r="22"
+ gradientTransform="matrix(0.3514481,0.895542,-1.0274841,0.4545455,12.925816,-8.0855683)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient7422"
+ id="radialGradient7831"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.2500003,0,-10.875016)"
+ cx="2"
+ cy="43.5"
+ fx="2"
+ fy="43.5"
+ r="2" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient7422"
+ id="radialGradient7833"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,1.2500003,44,-10.875014)"
+ cx="2"
+ cy="43.5"
+ fx="2"
+ fy="43.5"
+ r="2" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient7815"
+ id="linearGradient7835"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="41"
+ x2="6"
+ y2="46" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient8228"
+ id="linearGradient8234"
+ x1="10"
+ y1="40.999996"
+ x2="10"
+ y2="35.999996"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.6756757,0,0,0.7499986,-0.2162159,-3.8749457)" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="13.62465"
+ inkscape:cx="28.332311"
+ inkscape:cy="12.08918"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:window-width="1280"
+ inkscape:window-height="772"
+ inkscape:window-x="0"
+ inkscape:window-y="28"
+ inkscape:showpageshadow="false"
+ width="32px"
+ height="32px"
+ borderlayer="true"
+ gridtolerance="10000"
+ showgrid="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2173" />
+ </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" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Ebene 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ id="g7826"
+ style="opacity:0.46000001"
+ transform="matrix(0.6666666,0,0,1.1999995,0,-24.199975)">
+ <path
+ sodipodi:nodetypes="cscc"
+ id="path6654"
+ d="M 2,45.999998 C 0.896,45.999998 0,44.879998 0,43.499998 C 0,42.119997 0.896,40.999997 2,40.999997 C 2,40.999997 2,45.999998 2,45.999998 z"
+ style="color:#000000;fill:url(#radialGradient7831);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ sodipodi:nodetypes="cccc"
+ id="path7037"
+ d="M 48,43.5 C 48,44.88 47.104,46 46,46 C 46,46 46,40.999999 46,40.999999 C 47.104,40.999999 48,42.119999 48,43.5 z"
+ style="color:#000000;fill:url(#radialGradient7833);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+ <path
+ id="rect7432"
+ d="M 2,41 L 46,41 L 46,46 L 2,46 L 2,41 z"
+ style="fill:url(#linearGradient7835);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ <path
+ style="fill:#2e3436;fill-opacity:1;stroke:#000000;stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 4.2388889,2.5000001 L 1.5,3.8684212 L 1.5,28.500002 L 30.499999,28.500002 L 30.499999,3.8684212 L 27.76111,2.5000001 L 4.2388889,2.5000001 z"
+ id="rect2174"
+ sodipodi:nodetypes="ccccccc" />
+ <path
+ style="fill:url(#linearGradient3151);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 4.6666667,3 L 27.333332,3 L 30,5 L 2,5 L 4.6666667,3 z"
+ id="rect2761"
+ sodipodi:nodetypes="ccccc" />
+ <path
+ sodipodi:nodetypes="ccccccc"
+ id="path2757"
+ d="M 4.7696847,3.5000001 L 2.5000002,4.5250628 L 2.5000002,27.500001 L 29.500001,27.500001 L 29.500001,4.5250628 L 27.230316,3.5000001 L 4.7696847,3.5000001 z"
+ style="fill:none;fill-opacity:1;stroke:url(#radialGradient6652);stroke-width:1.00000024;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:url(#radialGradient4699);fill-opacity:1;stroke:#204a87;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 4.5,6.5000002 L 27.5,6.5000002 L 27.5,22.500001 L 4.5,22.500001 L 4.5,6.5000002 z"
+ id="rect2759" />
+ <path
+ style="opacity:0.4;fill:url(#linearGradient4307);fill-opacity:1;stroke:none;stroke-width:0.99999994;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 6,8 L 26,8 L 26,11.415255 C 26,11.415255 24.888889,9.7076275 18.777778,13.692092 C 12.666667,17.676555 6,14.898987 6,14.898987 L 6,8 z"
+ id="rect3536"
+ sodipodi:nodetypes="ccczcc" />
+ <path
+ style="fill:none;fill-opacity:1;stroke:url(#linearGradient5097);stroke-width:1.00000012;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 5.5000001,7.4999998 L 26.500001,7.4999998 L 26.500001,21.499999 L 5.5000001,21.499999 L 5.5000001,7.4999998 z"
+ id="rect3534" />
+ <path
+ style="fill:url(#linearGradient5872);fill-opacity:1;stroke:url(#linearGradient8234);stroke-width:1.00000036;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 3.5000001,23.500001 L 28.5,23.500001 L 28.5,26.499995 L 3.5000001,26.499995 L 3.5000001,23.500001 z"
+ id="rect5099" />
+ <path
+ id="path5874"
+ d="M 4.4999997,25.500001 L 24.500001,25.500001 L 24.500001,24.5 L 4.4999997,24.5 L 4.4999997,25.500001 z"
+ style="fill:none;fill-opacity:1;stroke:url(#linearGradient6263);stroke-width:0.99999988;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:#2e3436;fill-opacity:1;stroke:#555753;stroke-width:0.99999952;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1"
+ d="M 25.5,24.499999 L 27.499999,24.499999 L 27.499999,25.5 L 25.5,25.5 L 25.5,24.499999 z"
+ id="path7839" />
+ <rect
+ style="opacity:1;fill:#4e9a06;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7843"
+ width="1"
+ height="1"
+ x="27"
+ y="25" />
+ <rect
+ y="25"
+ x="26"
+ height="1"
+ width="1"
+ id="rect7845"
+ style="opacity:1;fill:#8ae234;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+</svg>