]> git.sur5r.net Git - minitube/commitdiff
Initial import
authorFlavio Tordini <flavio.tordini@gmail.com>
Wed, 17 Jun 2009 14:23:45 +0000 (16:23 +0200)
committerFlavio Tordini <flavio.tordini@gmail.com>
Wed, 17 Jun 2009 14:23:45 +0000 (16:23 +0200)
107 files changed:
.gitignore [new file with mode: 0644]
CHANGES [new file with mode: 0644]
INSTALL [new file with mode: 0644]
Info.plist [new file with mode: 0644]
LICENSE [new file with mode: 0644]
TODO [new file with mode: 0644]
images/app.png [new file with mode: 0644]
images/go-down.png [new file with mode: 0644]
images/go-previous.png [new file with mode: 0644]
images/go-up.png [new file with mode: 0644]
images/internet-web-browser.png [new file with mode: 0644]
images/pause.png [new file with mode: 0644]
images/play.png [new file with mode: 0644]
images/skip.png [new file with mode: 0644]
images/stop.png [new file with mode: 0644]
images/view-fullscreen.png [new file with mode: 0644]
locale/calculate_completion.sh [new file with mode: 0755]
locale/it_IT.ts [new file with mode: 0644]
locale/locale.pri [new file with mode: 0644]
locale/lupdate.sh [new file with mode: 0755]
locale/pt_BR.ts [new file with mode: 0644]
locale/ru_RU.ts [new file with mode: 0644]
minitube.icns [new file with mode: 0644]
minitube.pro [new file with mode: 0755]
resources.qrc [new file with mode: 0755]
src/AboutView.cpp [new file with mode: 0644]
src/AboutView.h [new file with mode: 0644]
src/Constants.h [new file with mode: 0755]
src/ListModel.cpp [new file with mode: 0755]
src/ListModel.h [new file with mode: 0755]
src/MainWindow.cpp [new file with mode: 0755]
src/MainWindow.h [new file with mode: 0755]
src/MediaView.cpp [new file with mode: 0644]
src/MediaView.h [new file with mode: 0644]
src/SearchView.cpp [new file with mode: 0644]
src/SearchView.h [new file with mode: 0644]
src/SettingsView.cpp [new file with mode: 0644]
src/SettingsView.h [new file with mode: 0644]
src/View.h [new file with mode: 0644]
src/faderwidget/FaderWidget.cpp [new file with mode: 0644]
src/faderwidget/FaderWidget.h [new file with mode: 0644]
src/global.h [new file with mode: 0644]
src/iconloader/.svn/entries [new file with mode: 0644]
src/iconloader/.svn/format [new file with mode: 0644]
src/iconloader/.svn/text-base/qticonloader.cpp.svn-base [new file with mode: 0644]
src/iconloader/.svn/text-base/qticonloader.h.svn-base [new file with mode: 0644]
src/iconloader/qticonloader.cpp [new file with mode: 0644]
src/iconloader/qticonloader.h [new file with mode: 0644]
src/main.cpp [new file with mode: 0755]
src/minisplitter.cpp [new file with mode: 0644]
src/minisplitter.h [new file with mode: 0644]
src/networkaccess.cpp [new file with mode: 0644]
src/networkaccess.h [new file with mode: 0644]
src/playlist/PrettyItemDelegate.cpp [new file with mode: 0644]
src/playlist/PrettyItemDelegate.h [new file with mode: 0644]
src/playlistwidget.cpp [new file with mode: 0644]
src/playlistwidget.h [new file with mode: 0644]
src/qtsingleapplication/QtLockedFile [new file with mode: 0644]
src/qtsingleapplication/QtSingleApplication [new file with mode: 0644]
src/qtsingleapplication/qtlocalpeer.cpp [new file with mode: 0644]
src/qtsingleapplication/qtlocalpeer.h [new file with mode: 0644]
src/qtsingleapplication/qtlockedfile.cpp [new file with mode: 0644]
src/qtsingleapplication/qtlockedfile.h [new file with mode: 0644]
src/qtsingleapplication/qtlockedfile_unix.cpp [new file with mode: 0644]
src/qtsingleapplication/qtlockedfile_win.cpp [new file with mode: 0644]
src/qtsingleapplication/qtsingleapplication.cpp [new file with mode: 0644]
src/qtsingleapplication/qtsingleapplication.h [new file with mode: 0644]
src/qtsingleapplication/qtsingleapplication.pri [new file with mode: 0644]
src/qtsingleapplication/qtsinglecoreapplication.cpp [new file with mode: 0644]
src/qtsingleapplication/qtsinglecoreapplication.h [new file with mode: 0644]
src/qtsingleapplication/qtsinglecoreapplication.pri [new file with mode: 0644]
src/searchlineedit.cpp [new file with mode: 0644]
src/searchlineedit.h [new file with mode: 0644]
src/searchparams.cpp [new file with mode: 0644]
src/searchparams.h [new file with mode: 0644]
src/spacer.cpp [new file with mode: 0644]
src/spacer.h [new file with mode: 0644]
src/thlibrary/imageblur.cpp [new file with mode: 0644]
src/thlibrary/imageblur.h [new file with mode: 0644]
src/thlibrary/thaction.cpp [new file with mode: 0644]
src/thlibrary/thaction.h [new file with mode: 0644]
src/thlibrary/thactiongroup.cpp [new file with mode: 0644]
src/thlibrary/thactiongroup.h [new file with mode: 0644]
src/thlibrary/thblackbar.cpp [new file with mode: 0644]
src/thlibrary/thblackbar.h [new file with mode: 0644]
src/thlibrary/thblackbutton.cpp [new file with mode: 0644]
src/thlibrary/thblackbutton.h [new file with mode: 0644]
src/thlibrary/thimage.cpp [new file with mode: 0644]
src/thlibrary/thimage.h [new file with mode: 0644]
src/thlibrary/thlibrary.pri [new file with mode: 0644]
src/thlibrary/thpainter.cpp [new file with mode: 0644]
src/thlibrary/thpainter.h [new file with mode: 0644]
src/updatechecker.cpp [new file with mode: 0644]
src/updatechecker.h [new file with mode: 0644]
src/urllineedit.cpp [new file with mode: 0644]
src/urllineedit.h [new file with mode: 0644]
src/video.cpp [new file with mode: 0644]
src/video.h [new file with mode: 0644]
src/videomimedata.cpp [new file with mode: 0644]
src/videomimedata.h [new file with mode: 0644]
src/videowidget.cpp [new file with mode: 0644]
src/videowidget.h [new file with mode: 0644]
src/youtubesearch.cpp [new file with mode: 0644]
src/youtubesearch.h [new file with mode: 0644]
src/youtubestreamreader.cpp [new file with mode: 0644]
src/youtubestreamreader.h [new file with mode: 0644]
svg/tv.svg [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..a2f6691
--- /dev/null
@@ -0,0 +1,7 @@
+build/
+Makefile*
+minitube.pro.user
+.settings/
+.DS_Store
+.cproject
+.project
diff --git a/CHANGES b/CHANGES
new file mode 100644 (file)
index 0000000..a30660f
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,26 @@
+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
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..98a7e6b
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,36 @@
+* 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/>.
diff --git a/Info.plist b/Info.plist
new file mode 100644 (file)
index 0000000..27b03c3
--- /dev/null
@@ -0,0 +1,18 @@
+<?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>
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..94a9ed0
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..7b30f6e
--- /dev/null
+++ b/TODO
@@ -0,0 +1,34 @@
+* 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
diff --git a/images/app.png b/images/app.png
new file mode 100644 (file)
index 0000000..18961b3
Binary files /dev/null and b/images/app.png differ
diff --git a/images/go-down.png b/images/go-down.png
new file mode 100644 (file)
index 0000000..dce3f15
Binary files /dev/null and b/images/go-down.png differ
diff --git a/images/go-previous.png b/images/go-previous.png
new file mode 100644 (file)
index 0000000..c37bc04
Binary files /dev/null and b/images/go-previous.png differ
diff --git a/images/go-up.png b/images/go-up.png
new file mode 100644 (file)
index 0000000..afb307b
Binary files /dev/null and b/images/go-up.png differ
diff --git a/images/internet-web-browser.png b/images/internet-web-browser.png
new file mode 100644 (file)
index 0000000..10d2ed4
Binary files /dev/null and b/images/internet-web-browser.png differ
diff --git a/images/pause.png b/images/pause.png
new file mode 100644 (file)
index 0000000..1e9f4d5
Binary files /dev/null and b/images/pause.png differ
diff --git a/images/play.png b/images/play.png
new file mode 100644 (file)
index 0000000..66f32d8
Binary files /dev/null and b/images/play.png differ
diff --git a/images/skip.png b/images/skip.png
new file mode 100644 (file)
index 0000000..52be942
Binary files /dev/null and b/images/skip.png differ
diff --git a/images/stop.png b/images/stop.png
new file mode 100644 (file)
index 0000000..a094787
Binary files /dev/null and b/images/stop.png differ
diff --git a/images/view-fullscreen.png b/images/view-fullscreen.png
new file mode 100644 (file)
index 0000000..00e6b83
Binary files /dev/null and b/images/view-fullscreen.png differ
diff --git a/locale/calculate_completion.sh b/locale/calculate_completion.sh
new file mode 100755 (executable)
index 0000000..1f1a4e0
--- /dev/null
@@ -0,0 +1,23 @@
+#!/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"
+
diff --git a/locale/it_IT.ts b/locale/it_IT.ts
new file mode 100644 (file)
index 0000000..25427b8
--- /dev/null
@@ -0,0 +1,376 @@
+<?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&apos;s life outside the browser!</source>
+        <translation>C&apos;è 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 &quot;Technology Preview&quot; 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 &lt;a href=&apos;%1&apos;&gt;donate via PayPal&lt;/a&gt; to support the continued development of %2.</source>
+        <translation>Per favore &lt;a href=&apos;%1&apos;&gt;fai una donazione con PayPal&lt;/a&gt; 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 &lt;a href=&apos;%1&apos;&gt;GNU General Public License&lt;/a&gt;</source>
+        <translation>Rilasciato sotto licenza &lt;a href=&quot;%1&quot;&gt;GNU General Public License&lt;/a&gt;</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>&amp;Back</source>
+        <translation>&amp;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>&amp;Stop</source>
+        <translation>&amp;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&amp;kip</source>
+        <translation>&amp;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>&amp;Pause</source>
+        <translation>&amp;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>&amp;Full Screen</source>
+        <translation>&amp;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>&amp;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>&amp;Remove</source>
+        <translation>&amp;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 &amp;Up</source>
+        <translation>Sposta &amp;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&apos;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 &amp;Down</source>
+        <translation>Sposta so&amp;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>&amp;Quit</source>
+        <translation>&amp;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>&amp;Website</source>
+        <translation>Sito &amp;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>&amp;Donate via PayPal</source>
+        <translation>Fai una &amp;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>&amp;About</source>
+        <translation>&amp;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>&amp;Search</source>
+        <translation>&amp;Ricerca</translation>
+    </message>
+    <message>
+        <location filename="../src/MainWindow.cpp" line="210"/>
+        <source>&amp;Application</source>
+        <translation>&amp;Applicazione</translation>
+    </message>
+    <message>
+        <location filename="../src/MainWindow.cpp" line="218"/>
+        <source>&amp;Playlist</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="../src/MainWindow.cpp" line="225"/>
+        <source>&amp;Video</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="../src/MainWindow.cpp" line="236"/>
+        <source>&amp;Help</source>
+        <translation>&amp;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>&amp;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 &amp;Full Screen</source>
+        <translation>&amp;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&apos;re watching &quot;%1&quot;</source>
+        <translation>Stai guardando &quot;%1&quot;</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 &lt;a href=&apos;%1&apos;&gt;%2&lt;/a&gt;,</source>
+        <translation>Benvenuto su &lt;a href=&quot;%1&quot;&gt;%2&lt;/a&gt;,</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 &lt;a href=&apos;%2&apos;&gt;update to version %3&lt;/a&gt;</source>
+        <translation>È disponibile una nuova versione di %1. &lt;a href=&apos;%2&apos;&gt;Aggiorna alla versione %3&lt;/a&gt;</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>
diff --git a/locale/locale.pri b/locale/locale.pri
new file mode 100644 (file)
index 0000000..e428eec
--- /dev/null
@@ -0,0 +1,24 @@
+# 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
diff --git a/locale/lupdate.sh b/locale/lupdate.sh
new file mode 100755 (executable)
index 0000000..102d707
--- /dev/null
@@ -0,0 +1,14 @@
+#!/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
+
diff --git a/locale/pt_BR.ts b/locale/pt_BR.ts
new file mode 100644 (file)
index 0000000..9a60521
--- /dev/null
@@ -0,0 +1,368 @@
+<?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&apos;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 &quot;Technology Preview&quot; release, do not expect it to be perfect.</source>
+        <translation>Este é um lançamento &quot;Prévio da Tecnologia&quot;, 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 &lt;a href=&apos;%1&apos;&gt;donate via PayPal&lt;/a&gt; to support the continued development of %2.</source>
+        <translation>Por favor, &lt;a href=&apos;%1&apos;&gt;doções via PayPal&lt;/a&gt; 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 &lt;a href=&apos;%1&apos;&gt;GNU General Public License&lt;/a&gt;</source>
+        <translation>Lançado sob a &lt;a href=&apos;%1&apos;&gt;Licença Pública Geral GNU&lt;/a&gt;</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>&amp;Back</source>
+        <translation>&amp;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>&amp;Stop</source>
+        <translation>&amp;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&amp;kip</source>
+        <translation>Pu&amp;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>&amp;Pause</source>
+        <translation>&amp;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>&amp;Full Screen</source>
+        <translation>&amp;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>&amp;YouTube</source>
+        <translation>&amp;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>&amp;Remove</source>
+        <translation>&amp;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 &amp;Up</source>
+        <translation>Mover para &amp;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 &amp;Down</source>
+        <translation>Mover para &amp;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>&amp;Quit</source>
+        <translation>&amp;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>&amp;Website</source>
+        <translation>&amp;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>&amp;Donate via PayPal</source>
+        <translation>&amp;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>&amp;About</source>
+        <translation>&amp;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>&amp;Search</source>
+        <translation>&amp;Pesquisar</translation>
+    </message>
+    <message>
+        <location filename="src/MainWindow.cpp" line="210"/>
+        <source>&amp;Application</source>
+        <translation>&amp;Aplicação</translation>
+    </message>
+    <message>
+        <location filename="src/MainWindow.cpp" line="218"/>
+        <source>&amp;Playlist</source>
+        <translation>&amp;Playlist</translation>
+    </message>
+    <message>
+        <location filename="src/MainWindow.cpp" line="225"/>
+        <source>&amp;Video</source>
+        <translation>&amp;Vídeo</translation>
+    </message>
+    <message>
+        <location filename="src/MainWindow.cpp" line="236"/>
+        <source>&amp;Help</source>
+        <translation>&amp;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>&amp;Play</source>
+        <translation>&amp;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 &amp;Full Screen</source>
+        <translation>Sair de &amp;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&apos;re watching &quot;%1&quot;</source>
+        <translation>Você está assistindo &quot;%1&quot;</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 &lt;a href=&apos;%1&apos;&gt;%2&lt;/a&gt;,</source>
+        <translation>Bem-vindo ao &lt;a href=&apos;%1&apos;&gt;%2&lt;/a&gt;,</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 &lt;a href=&apos;%2&apos;&gt;update to version %3&lt;/a&gt;</source>
+        <translation>Um nova versão de %1 está disponível. Por favor, &lt;a href=&apos;%2&apos;&gt;atualize para a versão %3&lt;/a&gt;</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>
diff --git a/locale/ru_RU.ts b/locale/ru_RU.ts
new file mode 100644 (file)
index 0000000..73dd256
--- /dev/null
@@ -0,0 +1,376 @@
+<?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&apos;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 &quot;Technology Preview&quot; release, do not expect it to be perfect.</source>
+        <translation>Эта версия - &quot;технический просмотр&quot;, не стоит ожидать совершенства.</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 &lt;a href=&apos;%1&apos;&gt;donate via PayPal&lt;/a&gt; to support the continued development of %2.</source>
+        <translation>&lt;a href=&apos;%1&apos;&gt;Поддержите через PayPal&lt;/a&gt; дальнейшую разработку %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 &lt;a href=&apos;%1&apos;&gt;GNU General Public License&lt;/a&gt;</source>
+        <translation>Выпущено на условиях &lt;a href=&apos;%1&apos;&gt;GNU General Public License&lt;/a&gt;</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>&amp;Back</source>
+        <translation>&amp;Назад</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>&amp;Stop</source>
+        <translation>&amp;Остановить</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&amp;kip</source>
+        <translation>П&amp;ропустить</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>&amp;Pause</source>
+        <translation>&amp;Приостановить</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>&amp;Full Screen</source>
+        <translation>&amp;На весь экран</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>&amp;YouTube</source>
+        <translation>&amp;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>&amp;Remove</source>
+        <translation>&amp;Удалить</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 &amp;Up</source>
+        <translation>В&amp;верх</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 &amp;Down</source>
+        <translation>В&amp;низ</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>&amp;Quit</source>
+        <translation>&amp;Выход</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>&amp;Website</source>
+        <translation>&amp;Домашняя страница</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>&amp;Donate via PayPal</source>
+        <translation>&amp;Поддержать через 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>&amp;About</source>
+        <translation>&amp;О программе</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>&amp;Search</source>
+        <translation>&amp;Поиск</translation>
+    </message>
+    <message>
+        <location filename="src/MainWindow.cpp" line="210"/>
+        <source>&amp;Application</source>
+        <translation>Пр&amp;иложение</translation>
+    </message>
+    <message>
+        <location filename="src/MainWindow.cpp" line="218"/>
+        <source>&amp;Playlist</source>
+        <translation>&amp;Список воспроизведения</translation>
+    </message>
+    <message>
+        <location filename="src/MainWindow.cpp" line="225"/>
+        <source>&amp;Video</source>
+        <translation>&amp;Видео</translation>
+    </message>
+    <message>
+        <location filename="src/MainWindow.cpp" line="236"/>
+        <source>&amp;Help</source>
+        <translation>Спр&amp;авка</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>&amp;Play</source>
+        <translation>Пр&amp;оиграть</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 &amp;Full Screen</source>
+        <translation>В&amp;ыйти из полноэкранного воспроизведения</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&apos;re watching &quot;%1&quot;</source>
+        <translation>Сейчас просматривается &quot;%1&quot;</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 &lt;a href=&apos;%1&apos;&gt;%2&lt;/a&gt;,</source>
+        <translation>Добро пожаловать в &lt;a href=&apos;%1&apos;&gt;%2&lt;/a&gt;,</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 &lt;a href=&apos;%2&apos;&gt;update to version %3&lt;/a&gt;</source>
+        <translation>Доступна новая версия %1. &lt;a href=&apos;%2&apos;&gt;Обновите до %3&lt;/a&gt;</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>
diff --git a/minitube.icns b/minitube.icns
new file mode 100644 (file)
index 0000000..0053278
Binary files /dev/null and b/minitube.icns differ
diff --git a/minitube.pro b/minitube.pro
new file mode 100755 (executable)
index 0000000..2dd1ccb
--- /dev/null
@@ -0,0 +1,104 @@
+# 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\\\"
+}
diff --git a/resources.qrc b/resources.qrc
new file mode 100755 (executable)
index 0000000..94b2d16
--- /dev/null
@@ -0,0 +1,14 @@
+<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>
diff --git a/src/AboutView.cpp b/src/AboutView.cpp
new file mode 100644 (file)
index 0000000..a94ecbf
--- /dev/null
@@ -0,0 +1,70 @@
+#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>&copy; 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
+
+}
diff --git a/src/AboutView.h b/src/AboutView.h
new file mode 100644 (file)
index 0000000..4344342
--- /dev/null
@@ -0,0 +1,29 @@
+#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
diff --git a/src/Constants.h b/src/Constants.h
new file mode 100755 (executable)
index 0000000..5d504b2
--- /dev/null
@@ -0,0 +1,14 @@
+#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
diff --git a/src/ListModel.cpp b/src/ListModel.cpp
new file mode 100755 (executable)
index 0000000..ac97835
--- /dev/null
@@ -0,0 +1,376 @@
+#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);
+
+}
diff --git a/src/ListModel.h b/src/ListModel.h
new file mode 100755 (executable)
index 0000000..5b1c9bf
--- /dev/null
@@ -0,0 +1,90 @@
+#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
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
new file mode 100755 (executable)
index 0000000..9d2b618
--- /dev/null
@@ -0,0 +1,490 @@
+#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);
+}
diff --git a/src/MainWindow.h b/src/MainWindow.h
new file mode 100755 (executable)
index 0000000..da46188
--- /dev/null
@@ -0,0 +1,104 @@
+#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
diff --git a/src/MediaView.cpp b/src/MediaView.cpp
new file mode 100644 (file)
index 0000000..1ebe801
--- /dev/null
@@ -0,0 +1,297 @@
+#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);
+}
diff --git a/src/MediaView.h b/src/MediaView.h
new file mode 100644 (file)
index 0000000..d810ce5
--- /dev/null
@@ -0,0 +1,89 @@
+#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__
diff --git a/src/SearchView.cpp b/src/SearchView.cpp
new file mode 100644 (file)
index 0000000..0569bea
--- /dev/null
@@ -0,0 +1,193 @@
+#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;
+}
diff --git a/src/SearchView.h b/src/SearchView.h
new file mode 100644 (file)
index 0000000..16865ee
--- /dev/null
@@ -0,0 +1,57 @@
+#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__
diff --git a/src/SettingsView.cpp b/src/SettingsView.cpp
new file mode 100644 (file)
index 0000000..4dc3c3c
--- /dev/null
@@ -0,0 +1,11 @@
+#include "SettingsView.h"
+
+SettingsView::SettingsView( QWidget *parent ) : QWidget(parent) {
+
+    QSettings settings;
+
+}
+
+void SettingsView::storeSettings() {
+
+}
diff --git a/src/SettingsView.h b/src/SettingsView.h
new file mode 100644 (file)
index 0000000..1e1a661
--- /dev/null
@@ -0,0 +1,30 @@
+#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
diff --git a/src/View.h b/src/View.h
new file mode 100644 (file)
index 0000000..ea701bd
--- /dev/null
@@ -0,0 +1,13 @@
+#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
diff --git a/src/faderwidget/FaderWidget.cpp b/src/faderwidget/FaderWidget.cpp
new file mode 100644 (file)
index 0000000..aaaa65a
--- /dev/null
@@ -0,0 +1,28 @@
+#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();
+
+}
diff --git a/src/faderwidget/FaderWidget.h b/src/faderwidget/FaderWidget.h
new file mode 100644 (file)
index 0000000..6dacaac
--- /dev/null
@@ -0,0 +1,32 @@
+#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
diff --git a/src/global.h b/src/global.h
new file mode 100644 (file)
index 0000000..291ef8a
--- /dev/null
@@ -0,0 +1,53 @@
+#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
diff --git a/src/iconloader/.svn/entries b/src/iconloader/.svn/entries
new file mode 100644 (file)
index 0000000..10222dc
--- /dev/null
@@ -0,0 +1,96 @@
+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
diff --git a/src/iconloader/.svn/format b/src/iconloader/.svn/format
new file mode 100644 (file)
index 0000000..ec63514
--- /dev/null
@@ -0,0 +1 @@
+9
diff --git a/src/iconloader/.svn/text-base/qticonloader.cpp.svn-base b/src/iconloader/.svn/text-base/qticonloader.cpp.svn-base
new file mode 100644 (file)
index 0000000..6f7a60c
--- /dev/null
@@ -0,0 +1,340 @@
+/****************************************************************************
+**
+** 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
diff --git a/src/iconloader/.svn/text-base/qticonloader.h.svn-base b/src/iconloader/.svn/text-base/qticonloader.h.svn-base
new file mode 100644 (file)
index 0000000..89fc1b2
--- /dev/null
@@ -0,0 +1,56 @@
+/****************************************************************************
+**
+** 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
diff --git a/src/iconloader/qticonloader.cpp b/src/iconloader/qticonloader.cpp
new file mode 100644 (file)
index 0000000..6f7a60c
--- /dev/null
@@ -0,0 +1,340 @@
+/****************************************************************************
+**
+** 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
diff --git a/src/iconloader/qticonloader.h b/src/iconloader/qticonloader.h
new file mode 100644 (file)
index 0000000..89fc1b2
--- /dev/null
@@ -0,0 +1,56 @@
+/****************************************************************************
+**
+** 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
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100755 (executable)
index 0000000..98d8ae6
--- /dev/null
@@ -0,0 +1,42 @@
+#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();
+}
diff --git a/src/minisplitter.cpp b/src/minisplitter.cpp
new file mode 100644 (file)
index 0000000..02fdacf
--- /dev/null
@@ -0,0 +1,85 @@
+/**************************************************************************
+**
+** 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);
+}
diff --git a/src/minisplitter.h b/src/minisplitter.h
new file mode 100644 (file)
index 0000000..51a1c01
--- /dev/null
@@ -0,0 +1,50 @@
+/**************************************************************************
+**
+** 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
diff --git a/src/networkaccess.cpp b/src/networkaccess.cpp
new file mode 100644 (file)
index 0000000..b48d14a
--- /dev/null
@@ -0,0 +1,161 @@
+#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));
+}
diff --git a/src/networkaccess.h b/src/networkaccess.h
new file mode 100644 (file)
index 0000000..42bfa11
--- /dev/null
@@ -0,0 +1,57 @@
+#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
diff --git a/src/playlist/PrettyItemDelegate.cpp b/src/playlist/PrettyItemDelegate.cpp
new file mode 100644 (file)
index 0000000..7954016
--- /dev/null
@@ -0,0 +1,211 @@
+#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();
+}
diff --git a/src/playlist/PrettyItemDelegate.h b/src/playlist/PrettyItemDelegate.h
new file mode 100644 (file)
index 0000000..a015364
--- /dev/null
@@ -0,0 +1,48 @@
+#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
+
diff --git a/src/playlistwidget.cpp b/src/playlistwidget.cpp
new file mode 100644 (file)
index 0000000..61b1336
--- /dev/null
@@ -0,0 +1,11 @@
+#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);
+}
diff --git a/src/playlistwidget.h b/src/playlistwidget.h
new file mode 100644 (file)
index 0000000..8d2bb29
--- /dev/null
@@ -0,0 +1,13 @@
+#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
diff --git a/src/qtsingleapplication/QtLockedFile b/src/qtsingleapplication/QtLockedFile
new file mode 100644 (file)
index 0000000..16b48ba
--- /dev/null
@@ -0,0 +1 @@
+#include "qtlockedfile.h"
diff --git a/src/qtsingleapplication/QtSingleApplication b/src/qtsingleapplication/QtSingleApplication
new file mode 100644 (file)
index 0000000..d111bf7
--- /dev/null
@@ -0,0 +1 @@
+#include "qtsingleapplication.h"
diff --git a/src/qtsingleapplication/qtlocalpeer.cpp b/src/qtsingleapplication/qtlocalpeer.cpp
new file mode 100644 (file)
index 0000000..1d494a7
--- /dev/null
@@ -0,0 +1,203 @@
+/****************************************************************************
+**
+** 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)
+}
diff --git a/src/qtsingleapplication/qtlocalpeer.h b/src/qtsingleapplication/qtlocalpeer.h
new file mode 100644 (file)
index 0000000..1169e1f
--- /dev/null
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** 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;
+};
diff --git a/src/qtsingleapplication/qtlockedfile.cpp b/src/qtsingleapplication/qtlockedfile.cpp
new file mode 100644 (file)
index 0000000..d4fad75
--- /dev/null
@@ -0,0 +1,199 @@
+/****************************************************************************
+**
+** 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.
+*/
diff --git a/src/qtsingleapplication/qtlockedfile.h b/src/qtsingleapplication/qtlockedfile.h
new file mode 100644 (file)
index 0000000..e229484
--- /dev/null
@@ -0,0 +1,101 @@
+/****************************************************************************
+**
+** 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
diff --git a/src/qtsingleapplication/qtlockedfile_unix.cpp b/src/qtsingleapplication/qtlockedfile_unix.cpp
new file mode 100644 (file)
index 0000000..f5997ba
--- /dev/null
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** 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();
+}
+
diff --git a/src/qtsingleapplication/qtlockedfile_win.cpp b/src/qtsingleapplication/qtlockedfile_win.cpp
new file mode 100644 (file)
index 0000000..8bbaa53
--- /dev/null
@@ -0,0 +1,213 @@
+/****************************************************************************
+**
+** 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);
+}
diff --git a/src/qtsingleapplication/qtsingleapplication.cpp b/src/qtsingleapplication/qtsingleapplication.cpp
new file mode 100644 (file)
index 0000000..2ab0bbd
--- /dev/null
@@ -0,0 +1,351 @@
+/****************************************************************************
+**
+** 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
+*/
diff --git a/src/qtsingleapplication/qtsingleapplication.h b/src/qtsingleapplication/qtsingleapplication.h
new file mode 100644 (file)
index 0000000..b73f5d6
--- /dev/null
@@ -0,0 +1,105 @@
+/****************************************************************************
+**
+** 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;
+};
diff --git a/src/qtsingleapplication/qtsingleapplication.pri b/src/qtsingleapplication/qtsingleapplication.pri
new file mode 100644 (file)
index 0000000..02de47e
--- /dev/null
@@ -0,0 +1,15 @@
+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
+}
diff --git a/src/qtsingleapplication/qtsinglecoreapplication.cpp b/src/qtsingleapplication/qtsinglecoreapplication.cpp
new file mode 100644 (file)
index 0000000..9b7fc40
--- /dev/null
@@ -0,0 +1,155 @@
+/****************************************************************************
+**
+** 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()
+*/
diff --git a/src/qtsingleapplication/qtsinglecoreapplication.h b/src/qtsingleapplication/qtsinglecoreapplication.h
new file mode 100644 (file)
index 0000000..f4738d5
--- /dev/null
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** 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;
+};
diff --git a/src/qtsingleapplication/qtsinglecoreapplication.pri b/src/qtsingleapplication/qtsinglecoreapplication.pri
new file mode 100644 (file)
index 0000000..d2d6cc3
--- /dev/null
@@ -0,0 +1,10 @@
+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)
+}
diff --git a/src/searchlineedit.cpp b/src/searchlineedit.cpp
new file mode 100644 (file)
index 0000000..8a42d8a
--- /dev/null
@@ -0,0 +1,242 @@
+/****************************************************************************
+**
+** 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());
+}
diff --git a/src/searchlineedit.h b/src/searchlineedit.h
new file mode 100644 (file)
index 0000000..b7b8005
--- /dev/null
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** 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
+
diff --git a/src/searchparams.cpp b/src/searchparams.cpp
new file mode 100644 (file)
index 0000000..896a0f1
--- /dev/null
@@ -0,0 +1,5 @@
+#include "searchparams.h"
+
+SearchParams::SearchParams() {
+    m_sortBy = SortByRelevance;
+}
diff --git a/src/searchparams.h b/src/searchparams.h
new file mode 100644 (file)
index 0000000..e8525c9
--- /dev/null
@@ -0,0 +1,31 @@
+#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
diff --git a/src/spacer.cpp b/src/spacer.cpp
new file mode 100644 (file)
index 0000000..d71367a
--- /dev/null
@@ -0,0 +1,7 @@
+#include "spacer.h"
+
+Spacer::Spacer(QWidget *parent, QWidget *child) : QWidget(parent) {
+    QBoxLayout *layout = new QHBoxLayout();
+    layout->addWidget(child);
+    setLayout(layout);
+}
diff --git a/src/spacer.h b/src/spacer.h
new file mode 100644 (file)
index 0000000..668001f
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef SPACER_H
+#define SPACER_H
+
+#include <QtGui>
+
+class Spacer : public QWidget
+{
+public:
+    Spacer(QWidget *parent, QWidget *child);
+};
+
+#endif // SPACER_H
diff --git a/src/thlibrary/imageblur.cpp b/src/thlibrary/imageblur.cpp
new file mode 100644 (file)
index 0000000..ff61109
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ *   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;
+}
+
diff --git a/src/thlibrary/imageblur.h b/src/thlibrary/imageblur.h
new file mode 100644 (file)
index 0000000..afb504c
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ *   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_ */
+
diff --git a/src/thlibrary/thaction.cpp b/src/thlibrary/thaction.cpp
new file mode 100644 (file)
index 0000000..6a1406d
--- /dev/null
@@ -0,0 +1,91 @@
+#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;
+}
diff --git a/src/thlibrary/thaction.h b/src/thlibrary/thaction.h
new file mode 100644 (file)
index 0000000..cde6034
--- /dev/null
@@ -0,0 +1,46 @@
+#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_ */
+
diff --git a/src/thlibrary/thactiongroup.cpp b/src/thlibrary/thactiongroup.cpp
new file mode 100644 (file)
index 0000000..33d1bfc
--- /dev/null
@@ -0,0 +1,63 @@
+#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;
+}
+
diff --git a/src/thlibrary/thactiongroup.h b/src/thlibrary/thactiongroup.h
new file mode 100644 (file)
index 0000000..4f391c0
--- /dev/null
@@ -0,0 +1,30 @@
+#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_ */
+
diff --git a/src/thlibrary/thblackbar.cpp b/src/thlibrary/thblackbar.cpp
new file mode 100644 (file)
index 0000000..8ea5179
--- /dev/null
@@ -0,0 +1,247 @@
+#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();
+}
+
diff --git a/src/thlibrary/thblackbar.h b/src/thlibrary/thblackbar.h
new file mode 100644 (file)
index 0000000..73e4739
--- /dev/null
@@ -0,0 +1,52 @@
+#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_ */
+
diff --git a/src/thlibrary/thblackbutton.cpp b/src/thlibrary/thblackbutton.cpp
new file mode 100644 (file)
index 0000000..aa50a07
--- /dev/null
@@ -0,0 +1,109 @@
+#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();
+}
+
diff --git a/src/thlibrary/thblackbutton.h b/src/thlibrary/thblackbutton.h
new file mode 100644 (file)
index 0000000..bf704e8
--- /dev/null
@@ -0,0 +1,32 @@
+#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_ */
+
diff --git a/src/thlibrary/thimage.cpp b/src/thlibrary/thimage.cpp
new file mode 100644 (file)
index 0000000..425fee7
--- /dev/null
@@ -0,0 +1,73 @@
+#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();
+}
+
+
diff --git a/src/thlibrary/thimage.h b/src/thlibrary/thimage.h
new file mode 100644 (file)
index 0000000..97e6c85
--- /dev/null
@@ -0,0 +1,26 @@
+#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_ */
+
+
diff --git a/src/thlibrary/thlibrary.pri b/src/thlibrary/thlibrary.pri
new file mode 100644 (file)
index 0000000..ce3ff80
--- /dev/null
@@ -0,0 +1,17 @@
+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
diff --git a/src/thlibrary/thpainter.cpp b/src/thlibrary/thpainter.cpp
new file mode 100644 (file)
index 0000000..3b97e21
--- /dev/null
@@ -0,0 +1,167 @@
+#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);
+}
+
diff --git a/src/thlibrary/thpainter.h b/src/thlibrary/thpainter.h
new file mode 100644 (file)
index 0000000..ad3e0b9
--- /dev/null
@@ -0,0 +1,57 @@
+#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_ */
+
diff --git a/src/updatechecker.cpp b/src/updatechecker.cpp
new file mode 100644 (file)
index 0000000..61f22d7
--- /dev/null
@@ -0,0 +1,62 @@
+#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;
+}
diff --git a/src/updatechecker.h b/src/updatechecker.h
new file mode 100644 (file)
index 0000000..8d6cf08
--- /dev/null
@@ -0,0 +1,42 @@
+#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
diff --git a/src/urllineedit.cpp b/src/urllineedit.cpp
new file mode 100644 (file)
index 0000000..a1064f9
--- /dev/null
@@ -0,0 +1,193 @@
+/****************************************************************************
+**
+** 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);
+}
diff --git a/src/urllineedit.h b/src/urllineedit.h
new file mode 100644 (file)
index 0000000..05667c4
--- /dev/null
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** 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
+
diff --git a/src/video.cpp b/src/video.cpp
new file mode 100644 (file)
index 0000000..96bb81f
--- /dev/null
@@ -0,0 +1,108 @@
+#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;
+}
+*/
diff --git a/src/video.h b/src/video.h
new file mode 100644 (file)
index 0000000..060305a
--- /dev/null
@@ -0,0 +1,82 @@
+#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
diff --git a/src/videomimedata.cpp b/src/videomimedata.cpp
new file mode 100644 (file)
index 0000000..38998f2
--- /dev/null
@@ -0,0 +1,13 @@
+#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";
+}
diff --git a/src/videomimedata.h b/src/videomimedata.h
new file mode 100644 (file)
index 0000000..ce06bd2
--- /dev/null
@@ -0,0 +1,26 @@
+#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
diff --git a/src/videowidget.cpp b/src/videowidget.cpp
new file mode 100644 (file)
index 0000000..1dffda5
--- /dev/null
@@ -0,0 +1,27 @@
+#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;
+    }
+}
diff --git a/src/videowidget.h b/src/videowidget.h
new file mode 100644 (file)
index 0000000..76ef108
--- /dev/null
@@ -0,0 +1,24 @@
+#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
diff --git a/src/youtubesearch.cpp b/src/youtubesearch.cpp
new file mode 100644 (file)
index 0000000..b1ae0fe
--- /dev/null
@@ -0,0 +1,67 @@
+#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;
+}
diff --git a/src/youtubesearch.h b/src/youtubesearch.h
new file mode 100644 (file)
index 0000000..77ed7d1
--- /dev/null
@@ -0,0 +1,32 @@
+#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
diff --git a/src/youtubestreamreader.cpp b/src/youtubestreamreader.cpp
new file mode 100644 (file)
index 0000000..0a3289c
--- /dev/null
@@ -0,0 +1,112 @@
+#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;
+}
diff --git a/src/youtubestreamreader.h b/src/youtubestreamreader.h
new file mode 100644 (file)
index 0000000..4cd3894
--- /dev/null
@@ -0,0 +1,21 @@
+#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
diff --git a/svg/tv.svg b/svg/tv.svg
new file mode 100644 (file)
index 0000000..5d2670d
--- /dev/null
@@ -0,0 +1,382 @@
+<?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>